因為HOJ在我寫完這題之後的submissions都沒有動靜(聽說HOJ快要壽終正寢了,好難過QAQ),所以現在來寫TIOJ了XD
這題題目敘述有點雜,還誤會題目意思打錯code,花了不少時間才理解成功@@
就是,給你一個數列,求最長區間的長度,使得這個區間的任意前綴和後綴和都$\geq 0$
首先,區間的任意前綴和後綴和都是區間和,可以用數列的前綴和(不是區間的)相減求得
假設一開始的$0$也算進去的話,我們可以把這些前綴和畫成有$N+1$個點的折線圖
而假設有一個區間符合題目的條件,把它對應到折線圖上面
那麼仔細思考就可以發現,在折線圖的這個區間裡面,區間左端是最低點,右端是最高點,中間不會允許任何一個點突破兩端的極值(不然就會有區間前綴或後綴和$<0$了)
為了方便說明,定義規則1為區間中間的值必須$\leq$右端點,規則2為區間中間的值必須$\geq$左端點
我們可以先預處理出每個左端點$u$符合規則2中最右邊的右端點$R[u]$,可以用單調對列實現。可以證明,在左端點到$R[u]$之間的所有點,都會是符合規則2的右端點
再來,對於每個左端點,可以維護一個符合規則1的右端點的集合,這一樣可以用單調對列從右到左掃實現
這樣,當左端點維護到$u$時,我們就可以在這個集合裡面進行二分搜,在符合規則1的右端點之中找出最大且$\leq R[u]$的點$v$,那麼$[u,v]$就是符合題目要求中左端點是$u$的最長區間了
在這些計算結果中,取最大的區間長度即可
時間複雜度:$O(N\log N)$
code:
#include<cstdio> #include<vector> #include<cassert> using namespace std; typedef long long LL; const LL INF=9223372036854775807LL; void getmax(int &a,const int b){if(b>a)a=b;} struct Stack { vector<int>data; int last; void clear(){data.clear(),last=-1;} void push(const int v){data.push_back(v),last++;} void pop(){data.pop_back(),last--;} int top()const{return data[last];} int lower_bound(const int v) { int l=0,r=last+1; while(l<r) { const int mid=(l+r)/2; if(data[mid]>=v)l=mid+1; else r=mid; } assert(r<=last); return data[r]; } }STK; int N; LL SUM[1000002]; int RIGHT[1000001]; int main() { // freopen("in.txt","r",stdin); int testcount;scanf("%d",&testcount); while(testcount--) { scanf("%d",&N); SUM[0]=0LL; for(int i=1;i<=N;i++)scanf("%lld",&SUM[i]),SUM[i]+=SUM[i-1]; STK.clear(); SUM[N+1]=-INF,STK.push(N+1); for(int i=N;i>=0;i--) { while(SUM[i]<=SUM[STK.top()])STK.pop(); RIGHT[i]=STK.top(); STK.push(i); } int ans=0; STK.clear(); SUM[N+1]=INF,STK.push(N+1); for(int i=N;i>=0;i--) { while(SUM[i]>SUM[STK.top()])STK.pop(); STK.push(i); getmax(ans,STK.lower_bound(RIGHT[i])-i); } printf("%d\n",ans); } return 0; }
沒有留言:
張貼留言
歡迎留言或問問題~
若您的留言中包含程式碼,請參考這篇
如果留言不見了請別慌,那是因為被google誤判成垃圾留言,小莫會盡快將其手動還原
注意:只有此網誌的成員可以留言。