作者:孤独游侠1976_127 | 来源:互联网 | 2023-10-10 18:09
今天在补AcWing周赛题目的时候遇到了一道很经典的区间和问题,因此写下本篇博客记录下来。
原题链接:4316. 合适数对 - AcWing题库
题目描述
输入样例1:
5 4
5 -1 3 4 -1
输出样例1:
5
输入样例2:
3 0
-1 2 -3
输出样例2:
4
输入样例3:
4 -1
-2 1 -2 3
输出样例3:
3
题目分析
题目描述的很简单,即我们需要统计区间内所有的两个端点,判断他们闭区间和是否小于t。
那么朴素的思想自然是前缀和了,但是如果是使用前缀和的话,我们不可避免的要枚举左右端点,这是的复杂度,注意到数据范围达到了,因此这个做法肯定会TLE。
那么我们就需要挖掘这道题目他的性质了。
首先,还是与要根据前缀和思想,记 维护的是前个数字的前缀和,根据题目要求,可以写出以下等式:
观察上面第二个式子,我们发现,如果我们枚举每一个区间的右端点,我们只需要统计一下有多少个小于右端点的点的前缀和大于 就行了。
至此,正解就呼之欲出了 :
我们只需要给所有前缀和维护一个树状数组,每次枚举右端点时,计算一下之前枚举的点所产生的前缀和中有多少个是小于 的,然后用已枚举点的数量减去这个值就是我们要统计的对象。
注意到本题取值范围非常大,而真正需要用到的点也只有2n个 (n对 和 ),因此需要离散化,使取值范围属于
AC代码
#include
#include
#include
#include
using namespace std ;
const int N = 4e5 + 10 ;
typedef long long LL ;
int n, tr[N] ;
LL s[N], t ; // 前缀和数组
vector nums ; // 离散化数组
int lowbit(int x)
{
return x & -x ;
}
void add(int x, int v)
{
for (int i = x ; i tr[i] += v ;
}
int sum(int x)
{
int res = 0 ;
for (int i = x ; i ; i -= lowbit(i))
res += tr[i] ;
return res ;
}
int get(LL x)
{
return lower_bound(nums.begin(), nums.end(), x) - nums.begin() + 1 ;
}
int main()
{
cin >> n >> t ;
for (int i &#61; 1 ; i <&#61; n ; i &#43;&#43; )
{
int x ;
cin >> x ;
s[i] &#61; s[i - 1] &#43; x ;
}
for (int i &#61; 0 ; i <&#61; n ; i &#43;&#43; )
{
nums.push_back(s[i]) ;
nums.push_back(s[i] - t) ;
}
sort(nums.begin(), nums.end()) ;
nums.erase(unique(nums.begin(), nums.end()), nums.end()) ;
LL res &#61; 0 ;
add(get(s[0]), 1) ;
for (int i &#61; 1 ; i <&#61; n ; i &#43;&#43; )
{
res &#43;&#61; i - sum(get(s[i] - t)) ;
add(get(s[i]), 1) ;
}
cout <
return 0 ;
}