題解:題意不再說了,題目很清楚的。
思路:因爲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; }