[考試反思]1024csp-s模擬測試86:消耗

%%%兩個沒素質的和一個萌兩小時AKc++

最近貌似老是能夠比較快速的拿下T1,而後T2打到考試結束。。。ide

T1是套路題沒什麼好說的。優化

T2是一個比較蠢的博弈題,我花了很長時間幹各類亂七八糟的事spa

什麼打表啊壓表啊找規律啊找必勝策略啊。。。code

由於時間複雜度的計算錯誤致使我浪費了大量時間乾沒用的事blog

最後發現刷表複雜度優秀就A了it

而後中途留了20分鐘左右暴打T3,由於打成子序列了40->0io

並且更惋惜的是其實已經很貼近正解思路了,就是沒好好想好好打event

而後雖然說最終名次看起來還不錯,可是實際上和上面直接就是100分的斷檔。class

我這輩子何時能AK一回啊。。。

及時而正確地計算複雜度,注意觀察真正有用的狀態數,實在不行跑一遍試試。

看題。無論時間有多少,都要好好看題。

 

T1:異或

位運算類的套路題。二進制基本每次都是按位討論。

由於異或運算的特殊性,每一位之間的貢獻互不影響。

而只有當一個數是0另外一個數是1時纔會有值。

這樣問題就轉化爲了[L,R]內第k位上是1的有多少個。

相似數位dp,直接求很差弄,咱們用[0,R]的減去[0,L-1]的就是答案。

從0開始的要好統計一些。

能夠發現某一位在二進制下的規律是00001111000011110000111100...

第k位的循環節長度是1<<k+1,其中有1<<k位是1。而後剩餘的部分就是不足1<<k+1位,

在剩下的這麼多位裏1的個數是max(0,w%(1<<k+1)-(1<<k)+1)

而後就沒了。我不知道怎麼數位dp作。

 1 #include<cstdio>
 2 #define mod 1000000007
 3 long long up(long long x){return x>0?x:0;}
 4 int main(){
 5     int t,l,r;long long ans=0,x;
 6     scanf("%d",&t);
 7     while(t--){
 8         scanf("%d%d",&l,&r);ans=0;
 9         for(int i=0;i<30;++i){
10             x=r/(1<<i+1)-(l-1)/(1<<i+1)<<i;
11             x+=up(r%(1<<i+1)-(1<<i)+1);
12             x-=up((l-1)%(1<<i+1)-(1<<i)+1);
13             (ans+=x*(r-l+1-x)%mod*(1<<i+1))%=mod;
14         }
15         printf("%lld\n",ans);
16     }
17 }
View Code

 

T2:取石子

博弈論?

反正一個比較明顯的結論就是

若是你能夠走向你個對手必敗的局面,你就必勝,不然你必敗。

因此咱們就用每個必敗狀態去擴展,擴展獲得全部的必勝狀態。

由於這道題裏必敗狀態不多(10900個),因此刷表效率很高。

 1 #include<cstdio>
 2 bool x[301][301][301];
 3 int main(){
 4     for(int i=0;i<=300;++i)for(int j=i;j<=300;++j)for(int k=j;k<=300;++k)if(!x[i][j][k]){
 5         for(int s=1;s<=7;++s)for(int a=1;a<=300;++a){
 6             int I=i+(s&1?a:0),J=j+(s&2?a:0),K=k+(s&4?a:0);
 7             if(I>300||J>300||K>300)break;
 8             if(I>J)I^=J^=I^=J;
 9             if(I>K)I^=K^=I^=K;
10             if(J>K)J^=K^=J^=K;
11             x[I][J][K]=1;
12         }
13     }
14     int t,a,b,c;scanf("%d",&t);
15     while(t--){
16         scanf("%d%d%d",&a,&b,&c);
17         if(a>b)a^=b^=a^=b;
18         if(a>c)a^=c^=a^=c;
19         if(b>c)b^=c^=b^=c;
20         puts(x[a][b][c]?"Yes":"No");
21     }
22 }
View Code

聽說有人不知道什麼叫刷表什麼叫填表?

若是你外層枚舉ijk,內層枚舉xyz

刷表就是用dp[i][j][k]去更新dp[x][y][z]

填表就是用dp[x][y][z]去更新dp[i][j][k]

看似區別不大,可是在這道題裏刷表是$O(10900M \times 7)$的,填表是$O(7 \times M^4)$的

由於刷表能利用必敗狀態少的這個性質(外層枚舉必敗狀態)

而填表利用不了(外層什麼都枚舉,而內層去尋找必敗狀態)

 

T3:優化

一個經常使用的技巧就是遇到絕對值取最大值時,直接把絕對值去掉,正的負的都來一遍更新max,最後最優決策不會變差。

這樣的話咱們考慮如何dp。

相鄰兩段必定是一個加一個減。

那麼相鄰三段一共就有4種狀態,中間那一段的貢獻分別是+2,0,0,-2

而後用secret的思路設4種狀態可作(據說還比較好作),而我寫的是FACE的思路。

一個$O(n^2k)$的思路是設dp[i][j][0/1]表示到位置i剛好是某一個段結尾,已經用了j個段,上一段是減是加。

枚舉ij,再枚舉上一段的終點,找中間區間內最優的新區間起點。

加一些ST表來O(1)獲得最優決策就行了,特殊處理最後一段就行了。

暴力就不細說了(稍懶,否則正解也寫不完),直接給上代碼吧。

 1 #include<cstdio>
 2 int max(int a,int b){return a>b?a:b;}
 3 int min(int a,int b){return a<b?a:b;}
 4 int n,k,dp[30004][202],dp2[30004][202],x[30004],sum[30004];
 5 int mn[40004][16],mx[40004][16],hi_bit[40004],ans;
 6 int qmax(int l,int r){
 7     int L=r-l+1,B=hi_bit[L];
 8     return max(mx[l][B],mx[r+1-(1<<B)][B]);
 9 }
10 int qmin(int l,int r){
11     int L=r-l+1,B=hi_bit[L];
12     return min(mn[l][B],mx[r+1-(1<<B)][B]);
13 }
14 int bg(int l,int r){
15     return sum[r]-qmin(l-1,r-1);
16 }
17 int sl(int l,int r){
18     return sum[r]-qmax(l-1,r-1);
19 }
20 int main(){
21     scanf("%d%d",&n,&k);
22     for(int i=1;i<=n;++i)scanf("%d",&sum[i]),sum[i]+=sum[i-1],mn[i][0]=mx[i][0]=sum[i];
23     for(int l=1;l<15;++l)for(int i=0;i<=n+1-(1<<l);++i)
24         mn[i][l]=min(mn[i][l-1],mn[i+(1<<l-1)][l-1]),
25         mx[i][l]=max(mx[i][l-1],mx[i+(1<<l-1)][l-1]);
26     for(int l=0;l<15;++l)for(int j=1<<l;j<1<<l+1;++j)hi_bit[j]=l;
27     for(int i=0;i<=n;++i)for(int j=0;j<=k;++j)dp[i][j]=dp2[i][j]=-1000000000;
28     dp[0][0]=dp2[0][0]=0;
29     for(int i=1;i<=n;++i)for(int j=1;j<=k;++j)for(int f=0;f<i;++f){
30         dp[i][j]=max(dp[i][j],dp[f][j-1]+(f?0:1)*bg(f+1,i)),
31         dp[i][j]=max(dp[i][j],dp2[f][j-1]+(f?2:1)*bg(f+1,i)),
32         dp2[i][j]=max(dp2[i][j],dp2[f][j-1]-(f?0:1)*sl(f+1,i)),
33         dp2[i][j]=max(dp2[i][j],dp[f][j-1]-(f?2:1)*sl(f+1,i));
34     }
35     for(int i=1;i<=n;++i)for(int f=0;f<i;++f)ans=max(ans,max(dp2[f][k-1]+bg(f+1,i),dp[f][k-1]-sl(f+1,i)));
36     printf("%d\n",ans);
37 }
T40

可是「剛好」這個限制條件是咱們思惟常見的一個誤區,其實「剛好」並無意義。

咱們把它改爲「至多/至少「的形式每每能讓問題簡單一些。

在這道題裏,咱們就能夠轉化爲上一段區間的右端點在i左邊(含),這樣的話其實並不影響咱們的決策。

有一個細節問題。就是最好在外層枚舉k那一維(就叫它j了),否則會十分十分的麻煩。。。

首先咱們須要特殊處理j=1的轉移,其實它的含義就是 到i爲止的 最大的子區間 的前綴最大值。

考慮具體作法,問題就是sum[i]-sum[p-1]。按照道理可能又須要枚舉p了。

但其實開一個變量存下sum到i以前時的最小值。這樣最大減最小就能獲得最大的。

這樣就能夠獲得dp[i][1][1]了,獲得dp[i][1][0]實際上是同樣的就不細說了。

而後接下來的轉移會麻煩一點,咱們考慮從j=x到j=x+1的轉移。

其中一個轉移式子是這樣的:

dp[i][j][1]=dp[f][j-1][0]+2*(sum[i]-sum[f])

這樣的話怎麼能不枚舉f來弄?

其實就和上面j=1的狀況同樣了,能夠發現dp[f][j-1][1]-2sum[f]與i毫無關聯,依舊是開一個變量維護前綴最大值,不斷更新就好。

其他的3個轉移式子只須要分別維護一個變量就好了。大體同理。

j=k的那一步轉移依舊須要特殊處理。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,k,dp[2][30004][202],s[30004],ans,_01,_00,_11,_10;//0- 1+
 4 int main(){
 5     scanf("%d%d",&n,&k);
 6     for(int i=1;i<=n;++i)scanf("%d",&s[i]),s[i]+=s[i-1];
 7     memset(dp,0xa0,sizeof dp);
 8     for(int i=1;i<=n;++i)
 9         dp[1][i][1]=max(dp[1][i-1][1],_01+s[i]),dp[0][i][1]=max(dp[0][i-1][1],_10-s[i]),
10         _10=max(_10,+s[i]),_01=max(_01,-s[i]);
11     for(int j=2;j<k;++j){
12         _01=_00=_11=_10=-1e9;
13         for(int i=1;i<=n;++i)
14             dp[1][i][j]=max(dp[1][i-1][j],max(_01+2*s[i],_11)),
15             dp[0][i][j]=max(dp[0][i-1][j],max(_10-2*s[i],_00)),
16             _10=max(_10,dp[1][i][j-1]+2*s[i]),_11=max(_11,dp[1][i][j-1]),
17             _01=max(_01,dp[0][i][j-1]-2*s[i]),_00=max(_00,dp[0][i][j-1]);
18     }
19     _01=_00=_11=_10=-1e9;
20     for(int i=1;i<=n;++i)
21         ans=max(ans,_01+s[i]),ans=max(ans,_10-s[i]),
22         _10=max(_10,dp[1][i][k-1]+s[i]),_01=max(_01,dp[0][i][k-1]-s[i]);
23     printf("%d\n",ans);
24 }
碼倒不長。。
相關文章
相關標籤/搜索