https://www.luogu.com.cn/problem/P2679
只能说是,超级好的一道例题
[NOIP2015 提高组] 子串
题目背景
NOIP2015 Day2T2
题目描述
有两个仅包含小写英文字母的字符串 \(A\) 和 \(B\)。
现在要从字符串 \(A\) 中取出 \(k\) 个互不重叠的非空子串,然后把这 \(k\) 个子串按照其在字符串 \(A\) 中出现的顺序依次连接起来得到一个新的字符串。请问有多少种方案可以使得这个新串与字符串 \(B\) 相等?
注意:子串取出的位置不同也认为是不同的方案。
由于答案可能很大,所以这里要求输出答案对 \(1000000007\) 取模的结果。
样例 #1
样例输入 #1
6 3 1
aabaab
aab
样例输出 #1
2
数据范围
对于第 1 组数据:\(1≤n≤500,1≤m≤50,k=1\);
对于第 2 组至第 3 组数据:\(1≤n≤500,1≤m≤50,k=2\);
对于第 4 组至第 5 组数据:\(1≤n≤500,1≤m≤50,k=m\);
对于第 1 组至第 7 组数据:\(1≤n≤500,1≤m≤50,1≤k≤m\);
对于第 1 组至第 9 组数据:\(1≤n≤1000,1≤m≤100,1≤k≤m\);
对于所有 10 组数据:\(1≤n≤1000,1≤m≤200,1≤k≤m\)。
分析:
定义状态f[i][j][k][0/1]表示字符串A的前i个字符和字符串B的前j个字符用了k个子串,第四维为1表示A字符串的第i个字符必须用,为0表示A字符串的第i个字符不能用,匹配上的方案数。
可以分析出当a[i] == b[j]时,可以拼到a[i-1],b[j-1]之后(a[i-1] == b[j-1]),不再多开一个子串;也可以多开一个。当a[i-1] != b[j-1],则必须多开一个子串。
f[i][j][k][1] =f[i-1][j-1][k-1][1] + f[i-1][j-1][k][1] + f[i-1][j-1][k-1][0];
当a[i] != b[j]时,
f[i][j][k][1] = 0;
考虑f[i][j][k][0],也就是不管a[i]和b[j]相不相同,a[i]都不使用,还得匹配的上,说明b[j]一定是前面匹配的,要么i-1匹配,要么更早的i。
f[i][j][k][0] = f[i-1][j][k][1] + f[i-1][j][k][0]
好了,到我认为最难的地方了,--> 边界:
f[i][j]由i-1,j-1推出来,因此,需要知道第0行,所有列,以及第0列,所有行的f[]值。
考虑f[0][j][0][0] = 0, f[0][j][0][1] = 0, 怎么匹配不上b[j]
f[i][0][0][0] = 1, A串第i个字符和B串第0个字符,不使用i,用0个子串,则方案合法,为1。
f[i][0][0][1] = 0, A串第i个字符和B串第0个字符,使用i,用0个子串,则方案不合法,为0。
所以有以下代码:
#include <bitsstdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1005;
const int M = 205;
bool now;
int n , m , k;
char a[N] , b[N];
ll f[1001][101][101][2];
int main () {
scanf("%d %d %d" ,&n , &m, &k);
scanf("%s %s" , a + 1 , b + 1);
for(int i = 0; i <= n; i++)
f[i][0][0][0] = 1, f[i][0][0][1] = 0;
for(int j = 0; j <= m; j++)
f[0][0][j][0] = 0, f[0][0][j][1] = 0;
f[0][0][0][0] = 1;//重写一遍,否则被覆盖
for(int i = 1 ; i <= n ; ++i) {
for(int j = 1 ; j <= min(i , m) ; ++j) {
for(int p = 1 ; p <= k ; ++ p) {
if(a[i] == b[j]) {
f[i][p][j][1] = f[i - 1][p][j - 1][1]+ f[i - 1][p - 1][j - 1][0]+ f[i - 1][p - 1][j - 1][1];
f[i][p][j][1] %= mod;
} else
f[i][p][j][1] = 0;
f[i][p][j][0] = (f[i-1][p][j][0] + f[i-1][p][j][1]);
f[i][p][j][0] %= mod;
}
}
}
printf("%lld\n" ,( f[n][k][m][0] + f[n][k][m][1]) % mod);
return 0;
}
由于f[]初始化为0,所以只需要初始化不是0的。
for(int i = 0; i <= n; i++) f[i][0][0][0] = 1;
滚动数组的时候,每一行只和上一行相关,并且每一行中的每一列都重新赋值了,所以不需要memset(f[i%2], 0 ,sizeof f[i%2]);
#include <bitsstdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1001;
const int M = 201;
bool now;
int n , m , k;
char a[N] , b[N];
ll f[3][201][201][2];
int main () {
scanf("%d %d %d" ,&n , &m, &k);
scanf("%s %s" , a + 1 , b + 1);
f[0][0][0][0] = f[1][0][0][0] = 1;
for(int i = 1 ; i <= n ; ++i) {
for(int j = 1 ; j <= min(i , m) ; ++j) {
for(int p = 1 ; p <= k ; ++p) {
if(a[i] == b[j]) {
f[i%2][p][j][1] = f[(i - 1)%2][p][j - 1][1]+ f[(i - 1)%2][p - 1][j - 1][0]+ f[(i - 1)%2][p - 1][j - 1][1];
f[i%2][p][j][1] %= mod;
}
else
f[i%2][p][j][1] = 0;
f[i%2][p][j][0] = (f[(i-1)%2][p][j][0] + f[(i-1)%2][p][j][1]);
f[i%2][p][j][0] %= mod;
}
}
}
printf("%lld\n" ,( f[n%2][k][m][0] + f[n%2][k][m][1]) % mod);
return 0;
}
没了,完结撒花w
放一张给我提示的图片,感谢作者