P2679 [NOIP2015 提高组] 子串 题解

发布时间 2023-07-28 15:10:24作者: tx_08

原题
\(题目大意\)
\(从字符串a中选出k个子串s_1,s_2,s_3...s_k使得s_1+s_2+s_3+...+s_k=b\)
\(求总方案数对10^9+7取模的结果\)
\(1\le |a|即n\le 1000,1 \le |b|即m\le 200,1\le k\le |b|\)
\(设f_{i,j,x}表示已经选到a的第i个字符,b的第j个字符,共选了x个子串的方案数\)
\(则可得转移式f_{i,j,x}=f_{i-1,j,x}\sum_{a[l,i]=b[r,j]}f_{l-1,r-1,x-1}\)
\(a[l,i]=b[r,j]意为a的l到i区间与b的r到j区间相等\)
\(但是如果这样转移,复杂度为O(nmk\cdot n\cdot m^2),显然过不了,考虑优化\)
\(观察可以发现,对于a[l,i]=b[r,j]的l,r枚举其实可以进行预处理\)
\(把a和b看作两条链\)
\(如样例aabaab和aab\)
\(即为\)
image
\(对于每组i,j,l,r,因为要求相等,所以i-l和j-r是相等的\)
\(可以由l向r连一条边,记录i-l的值\)
\(于是变成这样\)
image
\(理论上来说,这样最多跑出nm^2条边,但是因为在每一层x的枚举中,每条边都至多使用一次,所以复杂度降到了O(nm^2k+nm^2)\)
\(据这题的数据O约为1000*200^3=8,000,000,000=8*10^9\)
\(但是实际上这个复杂度很难跑满,在加上常数小,于是可以过?\)
\(但是观察f_{i,j,x}的状态,发现大小为nm^2=1000*200^2=4*10^7\)
\(显然MLE\)
\(但是看到转移式f_{i,j,x}=f_{i-1,j,x}\sum_{a[l,i]=b[r,j]}f_{l-1,r-1,x-1}\)
\(f_{i,j,x}的转移只用到了f_{l,r,x-1}\)
\(所以可以把第3维滚动掉\)
\(变成f_{l,r,0/1},空间为2nm=4*10^5\)
\(于是便愉快的AC了\)
image

点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct zi
{
	short l,r;
	int lt;
}d[4000009];
int head[1009];
int top;
void mkb(int x,short l,short r)
{
	top++;
	d[top].l=l;
	d[top].r=r;
	d[top].lt=head[x];
	head[x]=top;
}
const int mod=1e9+7;
short n,m,k;
int f[1002][202][2];
string a,b;
int main()
{
	cin>>n>>m>>k;
	cin>>a>>b;
	for(short i=1;i<=n;i++)
	for(short j=1;j<=m;j++)
	{
		string s1="",s2="";
		for(short len=1;i+len-1<=n&&j+len-1<=m;len++)
		{
			s1+=a[i+len-2];
			s2+=b[j+len-2];
			if(s1==s2)
			mkb(i,j,j+len-1);
			else
			break;
		}
	}
	for(short i=0;i<=n;i++)
	f[i][0][0]=1;
	for(short x=1;x<=k;x++)
	{
		for(short i=0;i<=n;i++)
			for(short j=0;j<=m;j++)
				f[i][j][x%2]=0;
		for(short i=1;i<=n;i++)
		{
			for(short j=1;j<=m;j++)
			f[i][j][x%2]=(f[i][j][x%2]+f[i-1][j][x%2])%mod;
			for(int opt=head[i];opt;opt=d[opt].lt)
			{
				short j=i+d[opt].r-d[opt].l;
				f[j][d[opt].r][x%2]=(f[j][d[opt].r][x%2]+f[i-1][d[opt].l-1][(x+1)%2])%mod;
			}
		}
	}
	cout<<f[n][m][k%2];
	return 0;
}