计蒜客-蒜场抽奖(AC自动机+状态压缩DP)
题解:题意不再说了,题目很清楚的。
思路:因为N<=10,所以考虑状态压缩 AC自动机中 val[1<<i]: 表示第i个字符串。AC自动机中fail指针是指当前后缀在其他串里面所能匹配的最长前缀的长度,然后我们在这里统计一下以该点结束所能包含的字符串的数量(就是在fail树中该点到根节点所经过的所有为单词结尾的点,在这里我们只要val[x] |= val[fail[x]]就行了,因为val[fail[x]]已经统计过 点fail[x]到根的值了)。
考虑dp[i][j][k]:表示长度为i,第j个状态点,k为包含的单词的状态, 是否存在。然后转移方程为:if( ch[m-1][j][k] ) ch[m][ ch[j][ char ] ][ k|val[ ch[j][char] ] ]=1;(char : 为第j个状态再往后走一步到达的状态);
我们的长度是一步一步走的,而且当前步数,仅有上一步确定,所以我们可以压缩步数为奇数偶数,变为:if( ch[ (m-1)&1 ][j][k] ) ch[ m&1 ][ ch[j][ char ] ][ k|val[ ch[j][char] ] ]=1;最后我们只要在第m步的每个状态包含不停字符串状态时的答案里面去最大值就行了。
参考代码:
#include<bits/stdc++.h> using namespace std; const int INF=0x3f3f3f3f; const int maxn=1010; int ch[maxn][4],val[maxn],fail[maxn]; int w[12],tot,n,m,Ans[maxn]; bool dp[2][maxn][1<<11]; char s[110]; void Init() { tot=1; memset(val,0,sizeof val); memset(ch[tot],0,sizeof ch[tot]); } void Insert(char *s,int x) { int len=strlen(s),u=0; for(int i=0;i<len;++i) { int c=s[i]-'a'; if(!ch[u][c]) {ch[u][c]=tot++;memset(ch[tot],0,sizeof ch[tot]);} u=ch[u][c]; } val[u]=1<<x; } void GetFail() { queue<int>q; for(int i=0;i<4;++i) if(ch[0][i]) q.push(ch[0][i]); while(!q.empty()) { int u=q.front();q.pop(); for(int i=0;i<4;++i) { int v=ch[u][i]; if(!v){ch[u][i]=ch[fail[u]][i];continue;} q.push(v); fail[v]=ch[fail[u]][i]; val[v]|=val[fail[v]]; } } } void Work() { dp[0][0][0]=1; for(int i=1;i<=m;++i) { memset(dp[i&1],0,sizeof dp[i&1]); for(int j=0;j<tot;++j) for(int k=0;k<4;++k) for(int z=0;z<(1<<n);++z) { if(dp[(i-1)&1][j][z]) dp[i&1][ch[j][k]][z|val[ch[j][k]]]=1; } } } int GetAns(int x) { int ans=0; for(int i=0;i<n;++i) if(x&(1<<i)) ans+=w[i]; return ans; } int main() { scanf("%d%d",&n,&m); Init(); for(int i=0;i<n;++i) scanf("%s%d",s,w+i),Insert(s,i); GetFail(); Work(); int res=-INF; for(int j=0;j<(1<<n);++j) Ans[j]=GetAns(j); for(int i=0;i<tot;++i) for(int j=0;j<(1<<n);++j) if(dp[m&1][i][j]) res=max(res,Ans[j]); if(res<0) puts("Unhappy!"); else printf("%d\n",res); return 0; }