题意:
有两个仅包含小写英文字母的字符串 A 和 B。
现在要从字符串 A 中取出 k 个互不重叠的非空子串,然后把这 k 个子串按照其在字符串 A 中出现的顺序依次连接起来得到一个新的字符串。
请问有多少种方案可以使得这个新串与字符串 B 相等?
注意:子串取出的位置不同也认为是不同的方案。
数据范围:
对于第 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
------------------------------------------------------我是分割线----------------------------------------------------
题解:一道状态不太明显的题,我们首先要寻找这道题的切入点。
观察这道题的特点,我们发现,相对于B,A中的每个字符显然有选与不选两种决策。
而对于这两个字符串,可以用两个指针表示,一个指向A的状态,一个指向B的状态。
于是这道题的状态就很被发现了。
设置状态:
设 F(i,j,k,1) 表示A的前i个字符中,匹配到B中当前第j个字符,一共使用了k段,选择当前第i个字符的方案数。
F(i,j,k,0) 表示A的前i个字符中,匹配到B中当前第j个字符,一共使用了k段,不选择当前第i个字符的方案数。
然后就是状态转移:
由状态,我们可以推出如下状态转移方程:
(1) 当Ai == Bj 时,F(i,j,k,0) = F(i-1,j,k,0)+ F(i-1,j,k,1).
即当前A中第i个数不作为答案的方案数 == 前i-1的方案数总和。
F(i,j,k,1) = F(i-1,j-1,k-1,0) + F(i-1,j-1,k-1,1) + F(i-1,j-1,k,1);
即当前A中第i个数作为答案的方案数 == A中前i-1个数匹配到B中第j-1个的方案数总和(i不接到前一个答案中) + A中前i-1个数方案(i接到前一个答案中)。
(2)当Ai != Bj 时, F(i,j,k,0) = F(i-1,j-1,k,0) + F(i-1,j-1,k,1).
同(1)中的转移。
F(i,j,k,1) = 0;
由于当前数不能作为答案,所以方案为0.
再确定边界条件,我们可以知道,F(i,0,0,0) = 1;即匹配B中0个字符的方案为1.
于是我们可以写出代码:
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
typedef pair<int, int> pii;
typedef double db;
const int mod = 1e9+7;
const int N = 1e6 + 50;
int n, m, p;
int f[1010][101][101][2];
char a[N], b[N];
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
return x*f;
}
void work(){
rep(i, 0, n) f[i][0][0][0] = 1;
rep(i, 1, n) rep(j, 1, m) rep(k, 1, p){
if(a[i] == b[j]) {
f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;
f[i][j][k][1] = (f[i-1][j-1][k-1][0] + (f[i-1][j-1][k-1][1] + f[i-1][j-1][k][1]) % mod) % mod;
}
else {
f[i][j][k][1] = 0;
f[i][j][k][0] = (f[i-1][j][k][0] + f[i-1][j][k][1])%mod;
}
}
printf("%d
", (f[n][m][p][1] + f[n][m][p][0])%mod);
}
void init(){
n = read(); m = read(); p = read();
scanf("%s%s", a+1, b+1);
}
int main(){
init();
work();
return 0;
}
但是我们发现,这份代码空间复杂度效率低下(2*n*m*k),无法通过此题,我们还需要优化。
于是乎,DP常用的空间优化:滚动数组优化就出现了。
观察DP转移方程,我们可以发现,每一个决策i只与前一个决策i-1有关,其他的空间都是多余的。
所以我们就可以用01方法表示。
AC代码如下:
#include<bits/stdc++.h>
#define ll long long
#define mp make_pair
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
using namespace std;
typedef pair<int, int> pii;
typedef double db;
const int mod = 1e9 + 7;
const int N = 1e6 + 50;
int n, m, p;
int f[2][220][220][2];
char a[N], b[N];
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar();}
while(ch >='0' && ch <='9') { x = (x<<3)+(x<<1)+(ch^48); ch = getchar();}
return x*f;
}
void work(){
int val = 1;
f[0][0][0][0] = f[1][0][0][0] = 1;
rep(i, 1, n) {
rep(j, 1, m) rep(k, 1, p){
if(a[i] == b[j]) {
f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;
f[val][j][k][1] = (f[val^1][j-1][k-1][0] + (f[val^1][j-1][k-1][1] + f[val^1][j-1][k][1]) % mod) % mod;
}
else {
f[val][j][k][1] = 0;
f[val][j][k][0] = (f[val^1][j][k][0] + f[val^1][j][k][1])%mod;
}
}
val ^= 1;
}
printf("%d
", (f[n&1][m][p][1] + f[n&1][m][p][0])%mod);
}
void init(){
n = read(); m = read(); p = read();
scanf("%s%s", a+1, b+1);
}
int main(){
init();
work();
return 0;
}
总结:这道题作为线性DP的练习题(NOIP的题),有一定的思维难度,对DP思维提升有很大的帮助。