題目大意:
給你一個由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——線段樹|樹狀數組|分治+尺取
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.