【XSY3896】相似(dp套dp,状压)

题面

相似

题解

可以发现,\(S\)\(T\) 相似,等价于它们的最长公共子序列长度至少为 \(n - k\)

首先考虑如何求出两个字符串的 \(\text{LCS}\)(最长公共子序列)。考虑 dp:

\(f_{i,j}\) 表示 \(S[1\sim i]\)\(T[1\sim j]\)\(\text{LCS}\) 长度,转移是显然的:

\[\begin{aligned} f_{i,j}= \begin{cases} f_{i-1,j-1}+1& \operatorname{if }S[i]=T[j]\\ \max(f_{i-1,j},f_{i,j-1})& \operatorname{if }S[i]\neq T[j] \end{cases} \end{aligned} \]

然后考虑如何拓展到 \(T\) 任意的情况。自然的想法是 dp 套 dp:

\(dp[j][f_{1,j},f_{2,j},\cdots,f_{n,j}]\) 表示只考虑 \(T\) 的前 \(j\) 位,那么这 \(j\) 位有多少种选取方式,使此时的 \(f_{*,j}\) 数组如 dp 下标所示。

这个状态设计显然是可以优化的,不难证得 \(f_{i+1,j}-f_{i,j}\in\{0,1\}\),又由于 \(f_{0,j}=0\),所以我们把 \(dp\) 数组的第二维变成差分数组 \(c_i=f_{i+1}-f_{i,j}\) 的 01 串状压即可。

考虑这个 dp 的时间复杂度,状态数是 \(O(n2^n)\) 的。直接暴力 DP 的时间复杂度就约为 \(O(2^n\operatorname{poly}(n))\)。考虑如何利用 \(k\) 很小这个性质进行优化。

首先考虑优化状态数。

考虑某个满足 \(|i-j|>k\)\(f_{i,j}\)(显然有 \(f_{i,j}\leq \min(i,j)\)),再考虑这个 \(f_{i,j}\) 转移到 \(f_{n,n}\) 时最大会变成多少:观察最上面给出的 \(f_{i,j}\) 递推式,显然按第一种情况转移值才会变大,而第一种情况最多转移 \(\min(n-i,n-j)\) 次,所以从 \(f_{i,j}\) 转移到 \(f_{n,n}\) 时的值不大于:

\[f_{i,j}+\min(n-i,n-j)\leq \min(i,j)+\min(n-i,n-j)=n-(\max(i,j)-\min(i,j))<n-k \]

所以这个 \(f_{i,j}\) 不可能使 \(f_{n,n}\geq n-k\)

于是,与其记录整个 \(f\) 数组,我们只需要记录 \(f_{j−k,j} , f_{j−k+1,j},\cdots, f_{j+k,j}\),这可以通过一个长度为 \(2k\) 的差分数组以及一个 \(f_{j,j}\) 得到。(这个差分数组也可以用 01 串状压表示)

又由于 \(f_{j,j}\) 的值必须介于 \([j − k, j]\) 之间才能使 \(f_{n,n}\geq n-k\),所以我们将 \(dp\) 数组的第二维换成一个数 \(x\in [0,k]\) 表示 \(f_{j,j}=j-k+x\),第三维换成差分数组的 01 串状压。此时 \(dp\) 的总状态数只有 \(O(nk2^{2k})\)

再考虑优化转移。

转移的过程是这样的:对于 \(dp[j][x][sta]\),枚举一个字符 \(c\)\(T[j+1]\),向 \(dp[j+1][x'][sta']\) 转移。(其中 \(x'\)\(sta'\) 需要我们计算出来)

为了方便转移,我们可以先预处理一个 pair 数组 \(turn[j][x][sta][match]\),表示当 \(dp[j][x][sta]\)\(dp[j+1][x'][sta']\) 转移,枚举的字符 \(c\)\(S[j-k+1],S[j-k+2],\cdots,S[j+k+1]\) 的匹配状态为 \(match\) 时,\(x'\)\(sta'\) 分别是多少。

也就是说,我们知道了 \(f_{j-k\sim j+k,j}\)\(match\),要求此时的 \(f_{j-k+1\sim j+k+1,j+1}\)

按照最上面的递推式正常转移即可。

然后你会发现一个很神奇的东西:要求出 \(x\)\(sta\) 的变化量根本不需要 \(j\),只需要 \(x\)\(sta\)

所以 \(turn\) 的第一维 \(j\) 可以直接去掉。

所以才叫预处理 \(\sout{turn}\)

注意 \(match\) 也是可以预处理的,然后此时预处理的时间是 \(O(k^22^{2k}2^{2k+1}+26nk)=O(k^22^{4k+1}+26nk)\),可以接受。

转移的时间优化到了 \(O(26nk2^{2k})\)

其实已经能跑过了,但有点卡。

所以可以进一步的优化:

你发现对于枚举的 \(c\),只要 \(c\) 不在 \(S[j-k+1],S[j-k+2],\cdots,S[j+k+1]\) 内出现过,\(match\) 就等于 \(0\),也就是说它们都相等,此时得到的 \(x'\)\(sta'\) 都是相同的。

所以说,假设 \(S[j-k+1],S[j-k+2],\cdots,S[j+k+1]\) 中有 \(num\) 种字符,那么我们一次算出 \(match=0\) 时的 \(x'\)\(sta'\),然后转移 \(dp[j+1][x'][sta']\gets dp[j+1][x'][sta']+(26-num)dp[j][x][sta]\) 即可。

易知 \(num\leq 2k+2\leq 9\),所以这部分我们枚举 \(c\)\(turn\) 数组转移即可。

转移的时间复杂度降低到了约 \(O(10nk2^{2k})\)

然后能 1s AC 了。(时限 2s)

#include<bits/stdc++.h>

#define K 5
#define N 30010
#define mod 998244353

using namespace std;

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<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

namespace modular
{
	inline int add(int x,int y){if((x+=y)>=mod)x-=mod; return x;}
	inline int dec(int x,int y){if((x-=y)<0)x+=mod; return x;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
}

using namespace modular;

struct data
{
	int x,sta;
}turn[K][260][520];

int n,k,s[N];
int match[N][26];
int dp[N][K][260];
int f[K<<1],g[K<<1];
char ss[N];
bool vis[30];

int main(){
	scanf("%s%d",ss+1,&k);
	n=strlen(ss+1);
	for(int i=1;i<=n;i++) s[i]=ss[i]-'A';
	int pow2k=(1<<(k<<1)),pow2k1=pow2k<<1;
	for(int i=1;i<=n;i++)
	{
		for(int c=0;c<26;c++)
		{
			int sta=0;
			for(int l=i+k;l>=i-k;l--)
			{
				if(l<1||l>n) sta<<=1;
				else sta=sta<<1|(c==s[l]);
			}
			match[i][c]=sta;
		}
	}
	for(int x=0;x<=k;x++)
	{
		for(int sta=0;sta<pow2k;sta++)
		{
			f[k]=x;
			for(int i=k+1;i<=2*k;i++) f[i]=f[i-1]+((sta>>(i-1))&1);
			for(int i=k-1;i>=0;i--) f[i]=f[i+1]-((sta>>i)&1);
			for(int mat=0;mat<pow2k1;mat++)
			{
				for(int i=1;i<=2*k+1;i++)
				{
					if((mat>>(i-1))&1) g[i]=f[i-1]+1;
					else g[i]=max(g[i-1],f[i]);
				}
				turn[x][sta][mat].x=g[k+1]-1;
				int nsta=0;
				for(int i=2*k;i>=1;i--) nsta=nsta<<1|(g[i+1]-g[i]);
				turn[x][sta][mat].sta=nsta;
			}
		}
	}
	dp[0][k][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int x=0;x<=k;x++)
		{
			for(int sta=0;sta<pow2k;sta++)
			{
				if(!dp[i][x][sta]) continue;
				int num=0;
				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
				{
					if(!vis[s[j]]) num++;
					vis[s[j]]=1;
				}
				int nx=turn[x][sta][0].x,nsta=turn[x][sta][0].sta;
				if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],mul(26-num,dp[i][x][sta]));
				for(int j=max(1,i+1-k);j<=min(n,i+1+k);j++)
				{
					if(!vis[s[j]]) continue;
					vis[s[j]]=0;
					nx=turn[x][sta][match[i+1][s[j]]].x,nsta=turn[x][sta][match[i+1][s[j]]].sta;
					if(nx>=0) dp[i+1][nx][nsta]=add(dp[i+1][nx][nsta],dp[i][x][sta]);
				}
			}
		}
	}
	int ans=0;
	for(int x=0;x<=k;x++)
		for(int sta=0;sta<pow2k;sta++)
			ans=add(ans,dp[n][x][sta]);
	printf("%d\n",ans);
	return 0;
}
/*
GUGUA
1
*/
posted @ 2022-10-30 14:02  ez_lcw  阅读(17)  评论(0编辑  收藏  举报