测试地址:所有公共子序列问题
做法:本题需要用到序列自动机+DP+高精度。
序列自动机,顾名思义,就是能识别一个字符串所有本质不同的子序列的自动机。构建的方法十分简单:只需要从字符串上的每个点,向后面每种字符第一次出现的位置连边即可。唯一要注意的是,就像是所有其他自动机一样,还是要多建立一个空点表示空串的。因此,令字符集大小为,字符串长度为,那么构建序列自动机的时间复杂度是的。
那么回到这题,我们很快想到建立两个序列自动机,然后求公共子序列数目,实际上就是两个自动机同时匹配,只走两边都可以走的边,问有多少种走法。令为第一个自动机匹配到点,第二个自动机匹配到点,后面能够匹配出的公共子序列数目,记忆化搜索转移即可。
然而答案可能很大,需要高精度,又因为空间的限制,需要压位,这样我们就可以通过此题了。
(非常奇怪,一开始在洛谷上交怎么都RE,结果同一份代码交到LOJ就A了…)
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
const int w=1000000000;
int n,m,k,nxta[3020][60],nxtb[3020][60],nx[60];
char a[3020],b[3020],path[3020];
bool vis[3020][3020]={0};
struct hd
{
int siz;
int s[21];
void output()
{
printf("%d",s[siz-1]);
for(int i=siz-2;i>=0;i--)
printf("%09d",s[i]);
}
}f[3020][3020];
hd operator + (hd a,hd b)
{
hd s=a;
for(int i=0;i<b.siz;i++)
s.s[i]+=b.s[i];
s.siz=max(a.siz,b.siz);
for(int i=0;i<s.siz;i++)
if (s.s[i]>=w)
{
s.s[i+1]+=s.s[i]/w;
s.s[i]%=w;
if (i==s.siz-1) s.siz++;
}
return s;
}
void build(char *s,int len,int (*nxt)[60])
{
memset(nx,0,sizeof(nx));
for(int i=len;i>=1;i--)
{
for(int j=0;j<=59;j++)
nxt[i][j]=nx[j];
nx[s[i]-'A']=i;
}
for(int j=0;j<=59;j++)
nxt[0][j]=nx[j];
}
void dfs(int len,int a,int b)
{
for(int i=1;i<=len;i++)
printf("%c",path[i]);
printf("
");
for(int i=0;i<=59;i++)
if (nxta[a][i]&&nxtb[b][i])
{
path[len+1]=i+'A';
dfs(len+1,nxta[a][i],nxtb[b][i]);
}
}
void dp(int a,int b)
{
if (vis[a][b]) return;
vis[a][b]=1;
memset(f[a][b].s,0,sizeof(f[a][b].s));
f[a][b].siz=1;
f[a][b].s[0]=1;
for(int i=0;i<=59;i++)
if (nxta[a][i]&&nxtb[b][i])
{
dp(nxta[a][i],nxtb[b][i]);
f[a][b]=f[a][b]+f[nxta[a][i]][nxtb[b][i]];
}
}
int main()
{
scanf("%d%d",&n,&m);
scanf("%s%s",a+1,b+1);
scanf("%d",&k);
build(a,n,nxta);
build(b,m,nxtb);
if (k) dfs(0,0,0);
dp(0,0);
f[0][0].output();
return 0;
}