1. 程式人生 > >制杖選手的解題回顧

制杖選手的解題回顧

RT,因為這個選手過於制杖,從學OI一來的一年多裡有很多好題被他

亂搞過去(其實基本沒有

因為某OJ資料水水過了(有一些)

卡時間(仍然基本沒有

抄題解(有一些)

腦子廢掉忘了(很多

過於超前學習(還是基本沒有

因為懶/沒事間等原因瞎寫題解(很多

......

等原因浪費掉了,於是他又開了這個坑=。=

POI 2008 賬本BBB

請食用BZOJ資料或去釘子交一發

可以發現對於一個序列我們有一個固定的取反次數來滿足總和的限制

那麼如何滿足字首和處處非負的限制呢?顯然只需要滿足最小的那個地方就可以了

因為還有移位操作,我們可以把這個賬本先看成一個環。斷環為鏈,然後單調佇列/前後綴拼起來 搞出來以每個位置開頭的最小字首和,然後O(n)列舉起點(用位移操作)再O(1)求代價更新答案。

總複雜度O(n)

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=2000005;
 7 int fsum[N],msum[N],deq[N];
 8 int n,f,b,p,q,ct,cnt;
 9 long long c1,c2,ans;
10 char str[N]; 
11 int Count(int pos)
12 {
13     int
minc=p+msum[pos];//所有時刻的最小值 14 if(ct<0) minc+=2*cnt;//如果少記賬需要補上 15 if(minc>=0) return cnt; 16 else return cnt+abs(minc)+abs(minc)%2;//把最小值那個地方補起來 17 } 18 int main() 19 { 20 scanf("%d%d%d%lld%lld%s",&n,&p,&q,&c1,&c2,str+1); 21 for(int i=1;i<=n;i++) 22 fsum[i+n]=fsum[i]=(str[i]=='
+')?1:-1; 23 for(int i=1;i<=2*n;i++) 24 fsum[i]+=fsum[i-1]; f=1,b=0; 25 for(int i=2*n;i;i--)//單調佇列搞出來以每個位置為起點的最小值 26 { 27 while(f<=b&&fsum[deq[b]]-fsum[i]>=0) b--; deq[++b]=i; 28 while(f<=b&&deq[f]-i+1>n) f++; 29 msum[i]=fsum[deq[f]]-fsum[i-1]; 30 } 31 ct=(fsum[n]+p-q)/2,cnt=abs(ct),ans=c1*Count(1);//注意因為是取反一次相當於+2 32 for(int i=2;i<=n;i++) 33 ans=min(ans,c1*Count(i)+c2*(n-i+1)); 34 printf("%lld",ans); 35 return 0; 36 }
View Code