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

该博客详细解析了BZOJ 3864题目的解题思路,主要探讨如何在字符集大小为4,长度为m的字符串中找到与指定字符串S的最长公共子序列长度为i的字符串数量。通过分析动态规划的状态转移方程,博主提出可以将长度为i的字符串与S的LCS问题进行状态压缩,并利用此方法实现dp套dp,从而避免了传统的二维dp矩阵。这种方法也被应用在其他类似题目中,如HDU 4352,展示了动态规划与计数问题结合的巧妙应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

发现这个状态十分不好定义,考虑 O ( n 2 ) O(n^2) O(n2)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]=max(f[i1][j],f[i][j1],f[i1][j1](S[i]==T[j]))
发现 f [ i ] [ j ] = f [ i ] [ j − 1 ] f[i][j] = f[i][j-1] f[i][j]=f[i][j1] f [ i ] [ j ] = f [ i ] [ j − 1 ] + 1 f[i][j] = f[i][j-1] + 1 f[i][j]=f[i][j1]+1
那么我们完全可以把长度为i的字符串与S,DP后 f [ i ] [ j ] − f [ i ] [ j − 1 ] f[i][j]-f[i][j-1] f[i][j]f[i][j1]的值用二进制状态压缩起来,每次枚举新加入的字符,再进行LCS的dp,得到新状态转移。
意思就是,LCS和计数这两个dp不是不可得兼,用状态存状压后LCS的dp值,实际的dp值计数。类似的还有用 n log ⁡ n n\log n nlogn单调栈求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]);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值