[bzoj 3864]Hero meet devil 题解

考虑裸的最长公共子序列。

int n,m,g[20][20];
char strA[20],strB[20];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
    g[i][j]=max(g[i-1][j],g[i][j-1]);
    g[i][j]=max(g[i][j],g[i-1][j-1]+(strA[i]==strB[j]));
}

然后大家可能都会做了,就没了,嗯。

我们可以求满足一种性质的字符串的个数,这种性质可以用 \(g[i][j]\) 描述。

\(f[i][一个奇怪的东西]\) 表示 \(g[i][j],1\le j\le n\) 的值为 那个奇怪的东西 的字符串的个数。

这个 那个奇怪的东西 不好表示,它是一个数组,需要想个办法将它压下来。

仔细想一想会发现,\(g[i][j-1]\le g[i][j]\le g[i][j-1]+1\) 。会发现 \(g[i][j]\) 的差分数组只有 0 和 1。那么我们可以用一个二进制数存储 这个奇怪的东西。

那么我们设 \(f[i][j]\) 表示长度为 \(i\) 的字符串,和 \(S\)\(g[i][j]\) 数组的差分数组为 \(j\) 的个数。

我们可以枚举 \(i,j\) ,枚举后面接的字符转移,转移的时候先将 二进制数 解密成 \(g[i][j]\),再仿造 最长公共子序列 的方法更新 \(g[i][j]\),最后再将 \(g[i][j]\) 加密成 二进制数 就行了。

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int Len,m,End,a[20],ans[20];
int sum[20],g[20],cnt1[33000];
int f[1003][33000],to[33000][5];
char str[20];
inline int read()
{
	int x=0,w=0;char ch=0;
	while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return w?-x:x;
}
int calc(int state,int ch)
{
	int temp=0;
	sum[0]=0;
	for(int i=0;i<Len;i++)
	sum[i+1]=sum[i]+(state>>i&1);
	memset(g,0,sizeof g);
	for(int i=1;i<=Len;i++){
		if(a[i]==ch)g[i]=sum[i-1]+1;
		g[i]=max(g[i],max(sum[i],g[i-1]));
	}
	for(int i=0;i<Len;i++)
	temp+=(g[i+1]-g[i])<<i;
	return temp;
}
int main()
{
	int T=read();
	for(int i=1;i<=32767;i++)
		cnt1[i]=cnt1[i>>1]+(i&1);
	while(T --> 0){
		scanf("%s",str+1);
		memset(f,0,sizeof f);
		memset(ans,0,sizeof ans);
		End=1<<(Len=strlen(str+1));
		m=read();
		for(int i=1;i<=Len;i++)
		if(str[i]=='A')a[i]=1;
		else if(str[i]=='G')a[i]=2;
		else if(str[i]=='C')a[i]=3;
		else a[i]=4;
		f[0][0]=1;
		for(int i=0;i<End;i++)
		for(int j=1;j<=4;j++)
			to[i][j]=calc(i,j);
		for(int l=0;l<m;l++)
		for(int i=0;i<End;i++)
		for(int c=1;c<=4;c++)
			(f[l+1][to[i][c]]+=f[l][i])%=mod;
		for(int i=0;i<End;i++)
			(ans[cnt1[i]]+=f[m][i])%=mod;
		for(int i=0;i<=Len;i++)
			printf("%d\n",ans[i]);
	}
}
posted @ 2021-05-23 08:46  zYzYzYzYz  阅读(63)  评论(0编辑  收藏  举报