P2679 子串
Posted xcg123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了P2679 子串相关的知识,希望对你有一定的参考价值。
原题链接 https://www.luogu.com.cn/problem/P2679
题目大意
给你两个字符串 A 和 B,问你有几种方案使得将 B 分成不重复 k 段后每段在 A 中依次出现;
题解
一般这种字符串 dp,还是两个字符串瞎搞的这种,状态设置是有套路的,然而我就不知道;
例如我们要求两个序列的 LCS,我们可以这样设置状态:
dp [ i ][ j ]:第一个序列的 1~i 和第二个序列的 1~j 的 LCS 是多少;
转移的话我们只需从前往后扫,按照每次添加一个字符的方式进行转移;
这个题也可以这么设置状态,只是多了一个维度而已,正所谓 zhx 说过:多一个限制就多一个维度!
状态设置
dp [ i ][ j ][ k ]:将 B 串的 1~j 分成不重复的 k 段,使得这 k 段在 A 中的 1~i 中依次出现的方案数是多少;
转移的话我们需要考虑分段的问题;
如果当前的 A [ i ] == B [ j ],那么说这个字符可以与上一个字符连起来作为一个更长的段,也可以独自成一段;如果 A [ i ] != B [ j ],那么就不能匹配,需要继续枚举下去;
但是这样求得的答案是不对的,因为有可能在 A [ i ] 的后面的某个位置 A [ i‘ ] 有 A [ i ] == A [ i‘ ] == B [ j ],而如果我们统计上 A [ i‘ ] 与 B [ j ] 的答案时就要懂得舍弃当前的 A [ i ] 来让 i 继续往下枚举;
我们再在基础上加一个维度:
dp [ i ][ j ][ k ][ 0/1 ]: 将 B 串的 1~j 分成不重复的 k 段,使得这 k 段在 A 中的 1~i 中依次出现,第 i 个字符选不选的方案数是多少;
什么意思呢?举个例子说一下啦:
我们发现 A [ i ] == B [ j ],根据上面的说法,我们现在有两种抉择:
1. 让这个 b 和前面的 a 共为一段;
2. 让这个 b 单独成一段;
但是我们也可以舍弃当前的 A [ i ] ,继续往下匹配:
这样,我们才能找全所有的情况 。
状态转移
分为两种情况来讨论:
①:A [ i ] == B [ j ]:
那么我们现在有三种抉择了:
<1> 让当前字符与前面的字符连成一段,前提条件就是我们不能舍弃 A [ i ] 和 A [ i-1 ]:
dp [ i ][ j ][ k ][ 1 ] += dp [ i-1 ][ j-1 ][ k ][ 1 ];
<2> 让当前字符独自成段,前提条件就是我们不能舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:
dp [ i ][ j ][ k ][ 1 ] += dp [ i-1 ][ j-1 ][ k-1 ][ 0 ] + dp [ i-1 ][ j-1 ][ k-1 ][ 1 ];
<3> 我们不选当前的 A [ i ],给后面的留个机会,前提条件是舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:
dp [ i ][ j ][ k ][ 0 ] += dp [ i-1 ][ j ][ k ][ 1 ] + dp [ i-1 ][ j ][ k ][ 0 ];
②:A [ i ] != B [ j ]:
<1> 毫无疑问,我们被迫舍弃 A [ i ],但 A [ i-1 ] 的情况无所谓:
dp [ i ][ j ][ k ][ 0 ] += dp [ i-1 ][ j ][ k ][ 0 ] + dp [ i-1 ][ j ][ k ][ 1 ];
<2> 哼,我偏要选!Sorry啦,方案数为 0 qwq:
dp [ i ][ j ][ k ][ 1 ] = 0;
发现舍弃 A [ i ] 时的转移方程是一样的,所以我们可以合到一块去,那么代码是长这个亚子滴:
for(int i=1;i<=n;i++) { for(int j=1;j<=m;j++) { for(int k=1;k<=K;k++) { dp[i][j][k][0]+=(dp[i-1][j][k][0]+dp[i-1][j][k][1])%mod; //舍弃A[i]时代码一样 if(A[i]==B[j]) //若相等,可以与前面的连成一段,也可以独自成段 dp[i][j][k][1]+=((dp[i-1][j-1][k][1]+dp[i-1][j-1][k-1][0])%mod+dp[i-1][j-1][k-1][1])%mod; else dp[i][j][k][1]=0; //不相等偏要选?方案数为0 } } }
边界设置
也就是刚开始 B 什么都没有的情况,那么对于任意的 i(i∈n),都有 dp [ i ][ 0 ][ 0 ][ 0 ] =1;
for(int i=0;i<=n;i++) dp[i][0][0][0]=1;
但是会 MLE,O(nm2)直接 boom!
考虑到每次转移只与 A 串的前一个字符有关,所以我们可以滚动数组,只需开两个空间就够了,分别存当前状态和上一个状态;
转移的时候不能写 += 了,而是要直接赋值,不然你懂得qwq;
还需要用到位运算的小技巧,我们可以用 ^ 来实现 0~1 的转化,也就是说如果 i 是当前状态,i^1 就是上一个状态;
Code:
#include<iostream> #include<cstdio> #include<cmath> using namespace std; int read() { char ch=getchar(); int a=0,x=1; while(ch<‘0‘||ch>‘9‘) { if(ch==‘-‘) x=-x; ch=getchar(); } while(ch>=‘0‘&&ch<=‘9‘) { a=(a<<1)+(a<<3)+(ch-‘0‘); ch=getchar(); } return a*x; } const int mod=1e9+7; const int N=1005; char A[N],B[N]; int n,m,K; long long dp[2][205][205][2]; int main() { n=read();m=read();K=read(); scanf("%s",A+1);scanf("%s",B+1); dp[0][0][0][0]=dp[1][0][0][0]=1; //边界只需更新这两个就好了 for(int l=1;l<=n;l++) { int i=l%2; //当前状态 for(int j=1;j<=m;j++) { for(int k=1;k<=K;k++) { dp[i][j][k][0]=(dp[i^1][j][k][0]+dp[i^1][j][k][1])%mod; //舍弃A[i]时代码一样 if(A[l]==B[j]) //注意这里不是A[i]!!! dp[i][j][k][1]=((dp[i^1][j-1][k][1]+dp[i^1][j-1][k-1][0])%mod+dp[i^1][j-1][k-1][1])%mod;//若相等,可以与前面的连成一段,也可以独自成段 else dp[i][j][k][1]=0; //不相等偏要选?方案数为0 } } } printf("%lld ",(dp[n%2][m][K][0]+dp[n%2][m][K][1])%mod); //这里也别忘了改 return 0; }
以上是关于P2679 子串的主要内容,如果未能解决你的问题,请参考以下文章