专栏文章

题解:P2679 [NOIP 2015 提高组] 子串

P2679题解参与者 3已保存评论 2

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
2 条
当前快照
1 份
快照标识符
@mipv00ln
此快照首次捕获于
2025/12/03 18:23
3 个月前
此快照最后确认于
2025/12/03 18:23
3 个月前
查看原文

P2679 [NOIP 2015 提高组] 子串

题目

给出两个字符串 AABB,在 AA 中取出 kk 个互不重叠的子串,使得这些子串按顺序拼接在一起时等于 BB,求方案数。

状态

一看是神秘的方案题,很容易想到 dp。为了方便,可以用数组 aabb 存下 AABB 的 ASCII 码方便转移和优化复杂度,这样做是正确的原因是只关注两个字符是否相等而不是其他性质。
我们可以令 fi,j,tf_{i,j,t} 表示此时枚举数组 aai1i-1 都已算完,到第 ii 项,枚举数组 bbj1j-1 项都已算完,到第 jj 项,还可以取出 tt 个子串时的状态。
这是常规的设计状态的思路,但我们很快发现,这样设计时不好辨别选取该项时是不是和上一个选的是同一个子串,于是我们便要增加维度。
fi,j,t,0/1f_{i,j,t,0/1},其中 iijjtt 意思同上,00 表示不取数组 aa 中的第 ii 项,11 表示取。
容易发现,该状态可以在转移的时候进行判断,于是就可以开始状态转移。

转移

转移的时候进行分类讨论。
  • i>ji > j 时,fi,j,t,0=fi,j,t,1=0f_{i,j,t,0} = f_{i,j,t,1} = 0
  • aibja_{i} \ne b_{j} 时,ii 不能匹配 jj,只能从前面的状态进行转移,即:
fi,j,t,0=fi1,j,t,0+fi1,j,t,1\Large{f_{i,j,t,0} = f_{i-1,j,t,0} + f_{i-1,j,t,1}}
  • ai=bja_{i} = b_{j} 时,可以用 aia_{i} 匹配 bjb_{j},此时要么 i1i-1 的选了可以成为一个子串,要么新开一个子串,即:
fi,j,t,0=fi1,j,t,0+fi1,j,t,1fi,j,t,1=fi1,j1,t,1+fi1,j1,t1,0+fi1,j1,t1,1\Large{f_{i,j,t,0} = f_{i-1,j,t,0} + f_{i-1,j,t,1}} \\ \Large{f_{i,j,t,1} = f_{i-1,j-1,t,1} + f_{i-1,j-1,t-1,0} + f_{i-1,j-1,t-1,1}}

Code

CPP
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e3+1 , M = 201 , inf = 1e9+7;

int n , m , k , a[N] , b[M];

LL f[N][M][M][2];

string in;

int main()
{
	ios::sync_with_stdio (false);
	
	cin >> n >> m >> k;
	
	cin >> in;
	
	for(int i = 1 ; i <= n ; i ++)
	{
		a[i] = in.at(i-1) - 'a' + 1;
	}
	
	cin >> in;
	
	for(int i = 1 ; i <= m ; i ++)
	{
		b[i] = in.at(i-1) - 'a' + 1;
	}
	
	f[0][0][0][0] = 1;
	
	for(int i = 1 ; i <= n ; i ++)
	{
		f[i][0][0][0] = 1;
		for(int j = 1 ; j <= m && j <= i ; j ++)
		{
			for(int t = 1 ; t <= k ; t ++)
			{
				f[i][j][t][0] = (f[i-1][j][t][0] + f[i-1][j][t][1]) % inf;
				if(a[i] == b[j])
				{
					f[i][j][t][1] = (f[i-1][j-1][t][1] + f[i-1][j-1][t-1][0] + f[i-1][j-1][t-1][1]) % inf;
				}			
			}
		}
	}
	
	cout << (f[n][m][k][0] + f[n][m][k][1]) % inf << "\n";
	
	return 0;
}
然而,只有 1010 分。
这么大的空间 1000×200×200×2=8×1071000 \times 200 \times 200 \times 2 = 8 \times 10^7 谁不迷糊。
可以发现每个状态的 ii 只依赖于 i1i-1,于是可以滚动数组优化。
用取模运算或交替覆盖方式访问滚动数组。这里取模 22 可以近似看作位运算 &。

AC Code

CPP
#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 1e3+1 , M = 201 , inf = 1e9+7;

int n , m , k , a[N] , b[M];

LL f[2][M][M][2];

string in;

int main()
{
	ios::sync_with_stdio (false);
	
	cin >> n >> m >> k;
	
	cin >> in;
	
	for(int i = 1 ; i <= n ; i ++)
	{
		a[i] = in.at(i-1) - 'a' + 1;
	}
	
	cin >> in;
	
	for(int i = 1 ; i <= m ; i ++)
	{
		b[i] = in.at(i-1) - 'a' + 1;
	}
	
	f[0][0][0][0] = f[1][0][0][0] = 1;
	
	for(int i = 1 ; i <= n ; i ++)
	{
		for(int j = 1 ; j <= m && j <= i ; j ++)
		{
			for(int t = 1 ; t <= k ; t ++)
			{
				f[i&1][j][t][0] = (f[(i-1)&1][j][t][0] + f[(i-1)&1][j][t][1]) % inf;
				if(a[i] == b[j])
				{
					f[i&1][j][t][1] = (f[(i-1)&1][j-1][t][1] + f[(i-1)&1][j-1][t-1][0] + f[(i-1)&1][j-1][t-1][1]) % inf;
				}
				else f[i&1][j][t][1] = 0;
        // 这里注意,因为把数组复用了,转移不了的要清零。
			}
		}
	}
	
	cout << (f[n&1][m][k][0] + f[n&1][m][k][1]) % inf << "\n";
	
	return 0;
}

评论

2 条评论,欢迎与作者交流。

正在加载评论...