AcWing 4316. 合适数对
给定一个长度为 n n n 的整数数列 a a a 1 1 1 , a a a 2 2 2 , … , a a a n n n 和一个整数 t t t。
请你判断共有多少个数对 ( l , r ) (l,r) (l,r) 同时满足:
1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1≤l≤r≤n
a a a l l l + + + a a a l + 1 l + 1 l+1 + + + … … … + + + a a a r − 1 r-1 r−1 + + + a a a r r r < t <t <t
输入格式
第一行包含两个整数 n n n 和 t t t。
第二行包含 n n n 个整数 a a a 1 1 1 , a a a 2 2 2 , … , a a a n n n
输出格式
一个整数,表示满足条件的数对的数量。
数据范围
前三个测试点满足
1
≤
n
≤
5
1≤n≤5
1≤n≤5
所有测试点满足
1
≤
n
≤
2
×
10
1≤n≤2×10
1≤n≤2×10
5
5
5 ,
∣
t
∣
|t|
∣t∣
≤
≤
≤
2
×
10
2×10
2×10
14
14
14 ,
∣
a
|a
∣a
i
i
i
∣
|
∣
≤
10
≤10
≤10
9
9
9
输入样例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
解法 i)(树状数组+离散化)
看到区间和小于 t t t 首先想到处理前缀数组
若本题的
a
a
a
i
i
i 全为非负整数 那么前缀数组存在单调递增性质可以通过双指针或是二分求解
但本题的
a
a
a
i
i
i 存在负数使得前缀数组不存在单调性故非正解
通过维护前缀数组得到
q
[
r
]
−
q
[
l
−
1
]
<
t
q[r]-q[l-1]<t
q[r]−q[l−1]<t
简单移项
q
[
r
]
−
t
<
q
[
l
−
1
]
q[r]-t<q[l-1]
q[r]−t<q[l−1]
这样就转化为枚举
q
[
r
]
−
t
q[r]-t
q[r]−t 动态维护前缀和
故通过数组数组查找比
q
[
r
]
−
t
q[r]-t
q[r]−t 大的数的个数
但本题数的取值范围
∣
t
∣
|t|
∣t∣
≤
≤
≤
2
×
10
2×10
2×10
14
14
14 ,
∣
a
|a
∣a
i
i
i
∣
|
∣
≤
10
≤10
≤10
9
9
9 使得值域过大数组数组开不下 但
n
n
n 的个数只有
2
×
10
2×10
2×10
5
5
5 故可以通过离散化处理求解
Code i)
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
const long long P=231;
const int maxn=200003;
#define endl "\n"
#define __T int csT;scanf("%d",&csT);while(csT--)
int n,cnt;
long long x,ans;
long long q[200003],ls[400004];
int tr[400003];
long long t;
int lowbit(int x)
{
return x&-x;
}
int get(long long x)//查询原数离散化之后的数
{
return lower_bound(ls+1,ls+cnt+1,x)-ls;
}
int add(int x,int v)
{
while(x<=cnt)
{
tr[x]+=v;
x+= lowbit(x);
}
}
int ask(int x)
{
int sum=0;
while(x>0)
{
sum+=tr[x];
x-= lowbit(x);
}
return sum;
}
int main()
{
scanf("%d%lld",&n,&t);
q[0]=0;
cnt=0;
ls[++cnt]=0;//不等式右侧q[l-1]的下标取值范围为0 ~ n-1 故需要将q[0]加入离散数组
for(int i=1;i<=n;++i)
{
scanf("%lld",&x);
q[i]=q[i-1]+x;
ls[++cnt]=q[i];
ls[++cnt]=q[i]-t;//将每个需要离散化的值都加入离散数组
}
sort(ls+1,ls+cnt+1);//将排序后的下标作为原数的离散化值
/*cnt=unique(ls+1,ls+1+cnt)-ls;
这一步是将离散化数组中的元素去重 但因本题总数量不多这一步加不加都行
值得注意的是unique的返回值是去重后尾元素的地址故需要将cnt更新以便树状数组的操作
unique的时间复杂度是O(n) 故没有选择加这步操作不过影响也不大
*/
ans=0;
add(get(0),1);//将q[0]的离散值提前加入树状数组中因为遍历q[r]的下标是从1开始的
for(int i=1;i<=n;++i)
{
ans+=i-ask(get(q[i]-t));//q[r]之前的q[l-1]有r个数
//满足q[r]-t<q[l-1] 的q[l-1]数量即为总共i个数减去不满足的数量ask(get(q[i]-t))
add(get(q[i]),1);//最后将q[r]加入数组数组
}
printf("%lld\n",ans);
return 0;
}
解法 ii)(CDQ分治)
通过维护前缀数组得到
q
[
r
]
−
q
[
l
−
1
]
<
t
q[r]-q[l-1]<t
q[r]−q[l−1]<t
简单移项
q
[
l
−
1
]
>
q
[
r
]
−
t
q[l-1]>q[r]-t
q[l−1]>q[r]−t
相交传统的CDQ分治这题的操作就是先进行比较得到答案再进行归并 值得注意的是 q [ l − 1 ] q[l-1] q[l−1] 的下标可以从 0 0 0 开始取即满足 0 > q [ r ] − t 0>q[r]-t 0>q[r]−t 也是合法数对 在处理前缀数组的时候统计即可
Code ii)
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
const long long P=231;
const int maxn=200003;
#define endl "\n"
#define __T int csT;scanf("%d",&csT);while(csT--)
int n;
long long t,x,ans;
long long q[200003],b[200003];
void cdq(int l,int r)
{
if(l==r)return;
int mid=l+r>>1;
cdq(l,mid);
cdq(mid+1,r);
int z1=l,z2=mid+1;
while(z1<=mid&&z2<=r)
{
if(q[z1]>q[z2]-t)
{
ans+=mid-z1+1;
++z2;
}
else ++z1;
}
z1=l;
z2=mid+1;
for(int i=l;i<=r;++i)
{
if(q[z1]<=q[z2]&&z1<=mid||z2>r)b[i]=q[z1++];
else b[i]=q[z2++];
}
for(int i=l;i<=r;++i)q[i]=b[i];
}
int main()
{
scanf("%d%lld",&n,&t);
q[0]=0;
ans=0;
for(int i=1;i<=n;++i)
{
scanf("%lld", &x);
q[i] = q[i - 1] + x;
if(q[i]-t<0)++ans;
}
cdq(1,n);
printf("%lld\n",ans);
return 0;
}
解法 iii)(平衡树)
然 鹅 又 鸽 了 好 几 天
那先浅放一 个pb_ds 的 STL 平衡树版本上来吧
思路和解法i的思路差不多都是动态维护当前前缀值在之前前缀中的排名 其实具体实现还可以用别的一些邪典的树如宗法树等等但 tcl 就不说了
Code iii)
该写法优化了计算答案的O(n)
下附另一版不是边读入边计算的但是读入数据有点多卡常加了快读才能 Ac
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace __gnu_pbds;
using namespace std;
const long long mod=998244353;
const int maxn=200003;
#define endl "\n"
#define __T int csT;scanf("%d",&csT);while(csT--)
int n;
long long t,x,ans;
long long q[200003];
tree<pair<long long,int>,null_type,less<pair<long long,int>>,rb_tree_tag,tree_order_statistics_node_update> tr;
int main()
{
scanf("%d%lld",&n,&t);
q[0]=0;
ans=0;
tr.insert({0,0});
for(int i=1;i<=n;++i)
{
scanf("%lld",&x);
q[i]=q[i-1]+x;
ans+=i-(tr.order_of_key({q[i]-t,n+1}));
tr.insert({q[i],i});
}
printf("%lld\n",ans);
return 0;
}
分开写版本
#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace __gnu_pbds;
using namespace std;
const long long mod=998244353;
const int maxn=200003;
#define endl "\n"
#define __T int csT;scanf("%d",&csT);while(csT--)
inline long long read()
{
long long x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=x*10+ch-'0';
ch=getchar();
}
return x*f;
}
int n;
long long t,x,ans;
long long q[200003];
tree<pair<long long,int>,null_type,less<pair<long long,int>>,rb_tree_tag,tree_order_statistics_node_update> tr;
int main()
{
scanf("%d%lld",&n,&t);
q[0]=0;
ans=0;
tr.insert({0,0});
for(int i=1;i<=n;++i)
{
x=read();
q[i]=q[i-1]+x;
}
for(int i=1;i<=n;++i)
{
ans+=i-(tr.order_of_key({q[i]-t,n+1}));
tr.insert({q[i],i});
}
printf("%lld\n",ans);
return 0;
}
后续不出意外的会更新一个手写平衡树的版本如果不咕咕咕的话!