题目大意:
给你一个由n个整数组成的数组a,请你计算二元组(l,r)的个数,其中(l,r)满足条件
Input
第一行两个整数n和t (1 ≤ n ≤ 200,000 , |t| ≤ 2e14 )
第二行为一个序列: a1,a2...an ,注意可能有负数和零
Output
二元组个数
题解:
解法一:树状数组|线段树
首先我们可以想想暴力的解法,先预处理出前缀和,然后求出枚举l,r,求出符合条件的二元组个数(即满足sum[j]-sum[i-1]<t)
然后我们考虑可以怎么优化,首先我们肯定要枚举l和r中的一个,假如我们用i来枚举r,那么我们需要求出[1,i]的前缀和中满足sum[i-1]>sum[j]-t的个数,转换一下求是求出[1,i]区间里面,sum值大于sum[j]-t的个数,这样我们就把问题转换成了一个很经典的树状数组或者线段树求[1,i]区间里面小于某个定值的个数。
因为数据范围比较大,所以我们需要先离散化,然后在维护一个数据结构求答案。
代码实现(树状数组):
#pragma GCC optimize(2) #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<cstdio> #include<cstdlib> #include<vector> #include<map> #include<set> #include<stack> #include<queue> #define PI atan(1.0)*4 #define E 2.718281828 #define rp(i,s,t) for (register int i = (s); i <= (t); i++) #define RP(i,t,s) for (register int i = (t); i >= (s); i--) #define ll long long #define ull unsigned long long #define mst(a,b) memset(a,b,sizeof(a)) #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define debug printf("ac\n"); using namespace std; inline int read() { int a=0,b=1; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') b=-1; c=getchar(); } while(c>='0'&&c<='9') { a=(a<<3)+(a<<1)+c-'0'; c=getchar(); } return a*b; } const int INF = 0x3f3f3f3f; const int N = 2e5+7; ll sum[N],a[N],b[N]; ll n,t; inline lowbit(int x){return x&-x;} inline void update(int pos,int val){ for(int i=pos;i<=n;i+=lowbit(i)) b[i]+=val; } inline ll query(int pos){ ll ans=0; for(int i=pos;i>=1;i-=lowbit(i)) ans+=b[i]; return ans; } vector<ll> v; int main(){ cin>>n>>t; ll res=0; rp(i,1,n){ cin>>a[i]; sum[i]=sum[i-1]+a[i]; v.pb(sum[i]); if(sum[i]<t) res++; } sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); rp(i,1,n){ int x=lower_bound(v.begin(),v.end(),sum[i])-v.begin()+1; int y=lower_bound(v.begin(),v.end(),sum[i]-t+1)-v.begin(); // cout<<x<<" "<<y<<endl; res+=(i-1-query(y)); update(x,1); } cout<<res<<endl; return 0; }
解法二:
分治+尺取法,应该算是这个问题的标准解法吧,毕竟还挺精妙的。
首先类似于归并排序的写法,把序列分成两半,然后分别进行排序,对于每一半里面的答案可以递归统计,因此我们只需要统计出跨过中间点的贡献,这时可以用尺取法处理求出答案。
至于为什么能尺取法?
因为排完序后两半的前缀和都是递增的,那么我们就可以从最左边开始枚举l,从中间开始枚举r,算出答案。
这里用到了inplace_merge,就是把两个有序递增的序列合并成了一个有序递增的序列
代码实现:
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 2e5+7; ll n,t; ll a[N]; ll ans=0; void merge(int l,int r){ if(l==r) return ; int m=l+r>>1; int i=l,j=m+1; merge(l,m);merge(m+1,r); for(;i<=m;i++,ans+=j-m-1) for(;j<=r&&a[j]<t+a[i];) j++; inplace_merge(a+l,a+m+1,a+r+1); } int main(){ cin>>n>>t; for(int i=1;i<=n;i++) cin>>a[i],a[i]+=a[i-1]; merge(0,n); cout<<ans<<endl; return 0; }
Codeforces1042D——线段树|树状数组|分治+尺取
最新推荐文章于 2020-09-04 19:03:18 发布