1. 程式人生 > >codeforces 938F(dp+高維前綴和)

codeforces 938F(dp+高維前綴和)

splay 一個 pre strlen namespace 通過 AC src 目前

題意:

  給一個長度為n的字符串,定義$k=\floor{log_2 n}$

  一共k輪操作,第i次操作要刪除當前字符串恰好長度為$2^{i-1}$的子串

  問最後剩余的字符串字典序最小是多少?

分析:

  首先很容易得到一個性質,那就是刪除的那些串是可以不交叉的
  很容易想到一個很簡單的dp

  dp[i][j]表示考慮原串的前i位,刪除狀態為j的情況下字典序最小的字符串(註意dp裏面保存的是個字符串)

  那麽很明顯就是個O(n^3logn)的dp,無法通過

  dp裏是一個字符串這個東西是很浪費時間而且很不優美的

  根據題解的做法,重新設計狀態

  dp[i][j]表示已經確定了最終字符串的前i位,目前刪除情況為j的情況下,字典序最小的字符串

  這樣設計狀態我們會發現一個性質,那就是如果dp[i][j]<dp[i][k],那麽dp[i][k]就不起作用了

  所以dp數組可以用bool值來表示該狀態是否為當前最小的字符串

  更新狀態的話,根據確定位數i和刪除位數j就知道那些"1"對應字符串的下一位是多少了,更新新的最小字符串

  然後我們要考慮當前階段給後面要刪除幾個數,這裏即使要求滿足若一個狀態的某個子集是真,那麽它就是真

  這用一個高維前綴和解決即可

技術分享圖片
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=5000
; 4 char s[maxn+5]; 5 bool dp[maxn+5][maxn+5]; 6 int n,l,m; 7 string ans; 8 int main() 9 { 10 scanf("%s",s); 11 n=strlen(s); 12 l=0; 13 while((1<<(l+1))<=n) ++l; 14 m=1<<l; 15 for(int j=0;j<m;++j) 16 dp[0][j]=1; 17 for(int i=1;i<=n-m+1
;++i) 18 { 19 for(int j=0;j<m;++j) dp[i][j]=dp[i-1][j]; 20 char mi=z; 21 for(int j=0;j<m;++j) 22 if(dp[i-1][j]) mi=min(mi,s[i-1+j]); 23 for(int j=0;j<m;++j) 24 if(dp[i][j]&&s[i-1+j]!=mi) dp[i][j]=0; 25 26 for(int j=0;j<m;++j) 27 for(int k=0;k<l;++k) 28 if(j&(1<<k)) dp[i][j]|=dp[i][j^(1<<k)]; 29 ans=ans+mi; 30 31 } 32 cout<<ans<<endl; 33 }
View Code

codeforces 938F(dp+高維前綴和)