HDU3341-AC自动机+dp

HDU3341

题目描述

给出 n n n 个模式串,最后给出一个主串,问你主串打乱重组的情况下,最多能够包含多少个模式串?(只有 A T G C ATGC ATGC四种字符)

题解

首先对模式串建立AC自动机
显然考虑枚举每种字符的数量
我们用 h a s [ n u m 1 ] [ n u m 2 ] [ n u m 3 ] [ n u m 4 ] has[num1][num2][num3][num4] has[num1][num2][num3][num4]表示四种字符的数量的情况
定义 d p [ i ] [ j ] = d p [ 当 前 是 第 i 种 字 符 数 量 情 况 ] [ 当 前 枚 举 到 A C 自 动 机 第 j 个 节 点 ] = 包 含 的 模 式 串 dp[i][j]=dp[当前是第i种字符数量情况][当前枚举到AC自动机第j个节点]=包含的模式串 dp[i][j]=dp[i][ACj]=
正常转移即可,细节较多

代码

#include<bits/stdc++.h>//细节较多 
using namespace std;
int tr[505][5],val[505],fail[505],has[41][41][41][41],dp[505][15009],n,ans,tot,cnt,num[5],a[5];
char s[50]; 
void clear(){
	tot=cnt=ans=0;
	memset(tr,0,sizeof(tr));
	memset(val,0,sizeof(val));
	memset(dp,-1,sizeof(dp));
	memset(fail,0,sizeof(fail));
	memset(has,0,sizeof(has));
	memset(num,0,sizeof(num));
}
void init(){
	scanf("%s",s+1);
	int len=strlen(s+1);
	for(int i=1;i<=len;i++){
		if(s[i]=='A') num[1]++;
		if(s[i]=='G') num[2]++;
		if(s[i]=='C') num[3]++;
		if(s[i]=='T') num[4]++;
	}
	for(int i=0;i<=num[1];i++)
		for(int j=0;j<=num[2];j++)
			for(int k=0;k<=num[3];k++)
				for(int l=0;l<=num[4];l++)
					has[i][j][k][l]=tot++;
}
void build(){
	int len=strlen(s+1),u=0;
	for(int i=1;i<=len;i++){
		int c;
		if(s[i]=='A') c=0;
		if(s[i]=='G') c=1;
		if(s[i]=='C') c=2;
		if(s[i]=='T') c=3;
		if(!tr[u][c]) tr[u][c]=++cnt;
		u=tr[u][c];
	}val[u]++;
}
void getfail(){
	queue<int>q;
	for(int i=0;i<4;i++) if(tr[0][i]) q.push(tr[0][i]);
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int i=0;i<4;i++){
			if(!tr[u][i]) tr[u][i]=tr[fail[u]][i];
			else{
				int x=tr[u][i],y=fail[u];q.push(x);
				while(y&&!tr[y][i]) y=fail[y];
				fail[x]=tr[y][i];val[x]+=val[fail[x]];//注意val值更新 
			}
		}
	}
}
void DP(int cas){
	dp[0][0]=0;
	for(int k1=0;k1<=num[1];k1++)
		for(int k2=0;k2<=num[2];k2++)
			for(int k3=0;k3<=num[3];k3++)
				for(int k4=0;k4<=num[4];k4++)
					for(int i=0;i<=cnt;i++){//注意枚举的顺序 
						if(dp[i][has[k1][k2][k3][k4]]==-1) continue;
 						a[1]=k1,a[2]=k2,a[3]=k3,a[4]=k4;
						for(int j=1;j<=4;j++){
							if(a[j]==num[j]) continue;
							a[j]++;
							int x=has[a[1]][a[2]][a[3]][a[4]];
							int y=has[k1][k2][k3][k4];
							int nxt=tr[i][j-1];
							dp[nxt][x]=max(dp[nxt][x],dp[i][y]+val[nxt]);
							a[j]--;
						}
					}
	for(int i=0;i<=cnt;i++)
		ans=max(ans,dp[i][tot-1]);
	printf("Case %d: %d\n",cas,ans);
}
signed main(){
	int cas=0;
	while(scanf("%d",&n)&&n){
		clear();cas++;
		for(int i=1;i<=n;i++){
			scanf("%s",s+1);
			build();
		}init();getfail();DP(cas);
	}return 0;
}

总结

1,对于给出字符数量限制的题目,我们通常在 d p dp dp的时候加维度表示字符数量的情况
2,注意内外循环的顺序,判断方法即我们要得到一个状态,它由另外的一个(或一层)状态得来,那么一定要先求出另外这个(这层)状态(好像挺抽象,感性理解下,实在不行每个都试一下看答案对不对 )

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值