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种字符数量情况][当前枚举到AC自动机第j个节点]=包含的模式串
正常转移即可,细节较多
代码
#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,注意内外循环的顺序,判断方法即我们要得到一个状态,它由另外的一个(或一层)状态得来,那么一定要先求出另外这个(这层)状态(好像挺抽象,感性理解下,实在不行每个都试一下看答案对不对 )