标签:
题意:
把一个字符串分割成多个小串,小串组成严格递增序列,n<=5000
这是一个DP。
s代表原串
dp[i][j]代表当前到i位置最后一个串是以j为开头的方案数。答案就是dp[n][1]+...+dp[n][n]
很容易得到dp[i][j]=dp[j-1][k]+dp[j-1][k+1]……dp[j-1][j-1] (i-j=j-k) 如果s[k-1....j-1] >=s[j....i], dp[i][j]+=dp[j-1][k-1]
这样dp的递推式就有了,很容易发现dp[i][j]的递推式是一个前缀和,假设我比较s[k-1....j-1]和s[j....i]是O(1) 那么就可以在n^2的复杂度里得到答案
所以问题转化为如何预处理以x为开头的串和以y为开头的串的大小(x<y)
令r[x][y]为以x开头的串和以y开头的串经过多少长度分出大小,is[x][y]为true代表x小,为false代表y小
if(r[x-1][y-1]>0) r[x][y]=r[x-1][y-1]-1,is[x][y]=is[x-1][y-1];
else 暴力跑出r[x][y]
所以对于每一个r[1][y]我们得出r[2][y+1]...r[n-y+1][n]的复杂度是O(n)
所以求出r[1][1]....r[1][n]
然后有了所有的is(x,y) 就可以O(1)比较以x和y开头的子串大小
具体代码
#include<iostream> #include<cstring> #include<cstdio> #include<map> #include<cmath> #include<algorithm> using namespace std; const int N=5005; const long long MOD=1000000007; long long dp[N][N]; int r[N][N]; bool is[N][N]; char s[N]; char a[N],b[N]; int l1,l2,n; int main() { int i,j,k; while(scanf("%d",&n)!=EOF) { scanf("%s",s+1); for(i=1;i<=n;i++) dp[i][1]=1; for(j=2;j<=n;j++) { is[1][j]=false; for(k=0;k+j<=n;k++) { if(s[1+k]>s[j+k]) {is[1][j]=false;r[1][j]=k;break;} if(s[1+k]<s[j+k]) {is[1][j]=true;r[1][j]=k;break;} } if(k+j>n) r[1][j]=n+1; } for(k=1;k<n;k++) { for(i=2;i+k<=n;i++) { if(r[i-1][i+k-1]) {is[i][i+k]=is[i-1][i+k-1];r[i][i+k]=r[i-1][i+k-1]-1;} else { is[i][i+k]=false; for(j=0;i+j<=n&&i+k+j<=n;j++) { if(s[i+j]>s[i+k+j]) {is[i][i+k]=false;r[i][i+k]=j;break;} if(s[i+j]<s[i+k+j]) {is[i][i+k]=true;r[i][i+k]=j;break;} } if(i+k+j>n) r[i][i+k]=n+1; } } } for(i=2;i<=n;i++) { k=i; if(s[i]==‘0‘) continue; long long sum=0; for(j=i;j<=n;j++) { if(k>0) sum+=dp[i-1][k]; if(sum>=MOD) sum%=MOD; k--; dp[j][i]+=sum; if(k>0&&s[k]==‘0‘) continue; int flag=0; if(k>0) { if(is[k][i]==true&&r[k][i]+k<i) flag=1; } if(flag) dp[j][i]+=dp[i-1][k]; if(dp[j][i]>=MOD) dp[j][i]%=MOD; } } long long ans=0; for(i=1;i<=n;i++) { ans+=dp[n][i]; if(ans>=MOD) ans%=MOD; } cout<<ans<<endl; } return 0; }
Codeforces 611D New Year and Ancient Prophecy DP
标签:
原文地址:http://www.cnblogs.com/Woo95/p/5094637.html