Jeanny
寂兮,寥兮,独立不改,周行而不殆

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

放一张给我提示的图片,感谢作者

posted on 2024-08-15 15:56  Jeanny  阅读(36)  评论(0)    收藏  举报