偷个懒,题解看这里:https://www.luogu.org/problemnew/solution/P2679
看作者 GuessYCB 写的
===2018.9.6===
这一次算理解深一点了,也不再觉得这题是神题了,只是思维难度稍微高一点。
首先定义状态dp[i][j][k]代表A里前i个字符挑出k个子串组成b的前j个字符的总方案数,那答案就是dp[n][m][k];
然后想怎么转移
1.a[i]==b[j]
dp[i][j][k]=dp[i-1][j-1][k-1] + dp[i-1][j-1][k] + dp[i-1][j][k]
考虑a[i]可以自己一个人组成子串,跟前面的a[i-1]想连组成子串和不选a[i]
2.a[i]!=b[j]
dp[i][j][k]=dp[i-1][j][k] (那就是不选a[i])
【乍一看是对的,但实际上【跟前面的a[i-1]相连组成子串的方案数时】用dp[i-1][j-1][k]表达不对】
因为这种情况要求a[i-1]必须被选中,但dp[i-1][j-1][k]指的是从A的前i-1个字符中挑出k个子串组成B的前j-1个字符,并没有规定一定要选a[i-1]。所以想到要再
多加一个状态0/1表示选不选a[i],另一种实现方式就是用我代码里的s数组。
那转移方程就是
1.a[i]==b[j]
dp[i][j][k][0]=dp[i-1][j][k][1](上一个用)+dp[i-1][j][k][0](上一个还不用)
dp[i][j][k][1]=dp[i-1][j-1][k][1](跟上一个连成一串) + dp[i-1][j-1][k-1][1](小团体,上一个用) + dp[i-1][j-1][k-1][0](小团体,上一个不用)
2.a[i]!=b[j]
dp[i][j][k][0] = dp[i-1][j][k][1] + dp[i-1][j][k][0] 因为不能就不选a[i]所以不受影响
dp[i][j][k][1] = 0 选不了
dp[i][j][k][0]这个转移的方式有点像前缀和,因为不选a[i]的话,那方案数就是选所有可能的字符k(k在1-i-1之间),把他当作第一个加入k子串里的字符。
k不一样对应的方案一定不一样,所以不会重复,就是前缀和。
我说的这个转移方程其实跟我代码里写的转移方程不一样,两者都是对的。。
1 #include<iostream> 2 #include<map> 3 using namespace std; 4 5 int n,m,k; 6 char A[1005],B[205]; 7 long long mod = 1000000007; 8 9 long long f[2][205][205],s[2][205][205];// s[i][j][k]代表A里前i个挑k个子串 拼成B里前j个的方案数,一定用a[i] 10 // f[i][j][k]可用可不用a[i] 11 //滚动数组消掉i 12 // f[i][0][0]=1 13 int main(){ 14 cin>>n>>m>>k; 15 for(int i=1;i<=n;i++) cin>>A[i]; 16 for(int i=1;i<=m;i++) cin>>B[i]; 17 18 int now=1,last=0; 19 f[0][0][0]=1; 20 for(int i=1;i<=n;i++){ 21 f[now][0][0]=1; 22 for(int j=1;j<=min(i,m);j++){ 23 for(int p=1;p<=min(j,k);p++){//目标每一个状态都有意义 24 if( A[i]==B[j] ) s[now][j][p] = (f[last][j-1][p-1]+s[last][j-1][p])%mod ; 25 else s[now][j][p]=0; 26 f[now][j][p] = (s[now][j][p]+f[last][j][p])%mod; 27 } 28 } 29 now=!now; last=!last; 30 } 31 32 cout<<f[last][m][k]; 33 34 return 0; 35 }