BZOJ 3864: Hero meet devil (从dp性质实现dp套dp)

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/qq_35950004/article/details/87863099

题意:求长度为m的,字符集大小为4的,字符串,中,与字符串S(|S|<=15)的最长公共子序列长度=i的字符串数量。 i   0 S i \ \in 0 \to |S|

发现这个状态十分不好定义,考虑 O ( n 2 ) O(n^2) LCS的过程:
f [ i ] [ j ] = m a x ( f [ i 1 ] [ j ] , f [ i ] [ j 1 ] , f [ i 1 ] [ j 1 ] ( S [ i ] = = T [ j ] ) ) f[i][j] = max(f[i-1][j] , f[i][j-1] , f[i-1][j-1] * (S[i] == T[j]))
发现 f [ i ] [ j ] = f [ i ] [ j 1 ] f[i][j] = f[i][j-1] f [ i ] [ j ] = f [ i ] [ j 1 ] + 1 f[i][j] = f[i][j-1] + 1
那么我们完全可以把长度为i的字符串与S,DP后 f [ i ] [ j ] f [ i ] [ j 1 ] f[i][j]-f[i][j-1] 的值用二进制状态压缩起来,每次枚举新加入的字符,再进行LCS的dp,得到新状态转移。
意思就是,LCS和计数这两个dp不是不可得兼,用状态存状压后LCS的dp值,实际的dp值计数。类似的还有用 n log n n\log n 单调栈求LIS的奇妙贪心做状态,实际的dp值计数的题目(HDU 4352 LIS + 数位DP)。

这类题目的存在说明了dp套dp不是ddp,不是动态2规划,但也是毒瘤题。
AC Code:

#include<bits/stdc++.h>
#define maxn 16
#define mod 1000000007
using namespace std;

char s[maxn],mc[5]="ATGC";
int m,nxt[30][1<<15],f[2][1<<15],ans[maxn];

void add(int &a,int b){ a=a+b;a>=mod?a-=mod:0; }

int main()
{
	int T;
	for(scanf("%d",&T);T--;)
	{
		scanf("%s%d",s,&m);
		int n = strlen(s);
		for(int i=0;i<4;i++)
			for(int sta=0;sta<(1<<n);sta++)
			{
				int nsta=0;
				for(int j=0,fpd=0,fd=0,fu=0,fpu=0;j<n;j++)
				{
					fpd=fd;
					fd += (sta>>j&1);
					fpu=fu;
					fu = max(fu , fd);
					if(mc[i] == s[j]) fu = max(fu,fpd+1);
					nsta |= (fu-fpu)<<j;
				}
				nxt[i][sta]=nsta;
			}
	
		int now = 1,pre = 0;
		memset(f[pre],0,sizeof f[pre]);
		f[pre][0] = 1;
		for(;m--;swap(now,pre))
		{
			memset(f[now],0,sizeof f[now]);
			for(int sta=0;sta<(1<<n);sta++)
				if(f[pre][sta])
					for(int i=0;i<4;i++)
						add(f[now][nxt[i][sta]] , f[pre][sta]); 	
		}
		memset(ans,0,sizeof ans);
		for(int sta=0;sta<(1<<n);sta++)
			add(ans[__builtin_popcount(sta)] , f[pre][sta]);
		for(int i=0;i<=n;i++)
			printf("%d\n",ans[i]);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/87863099