HDU 3341 Lost's revenge ( Trie图 && 状压DP && 数量限制类型 )
题意 : 给出 n 个模式串,最后给出一个主串,问你主串打乱重组的情况下,最多能够包含多少个模式串。
分析 : 如果你做过类似 Trie图 || AC自动机 + DP 类似的题目的话,那么这道题相对之前的对于主串的“构造”过程加上了一个限制,那就是字符的元素的有限制的,那么DP的状态就不能用长度来表示状态( 类比 POJ 2778 ),所以写出了一个错误的根据长度DP的代码
for(int i=0; i<len; i++){ for(int j=0; j<ac.Size; j++){ if(dp[i][j] >= 0){ for(int k=0; k<4; k++){ if(Lter[i][j][k] > 0){///表示 i、j 状态下,0123代表的“ATGC”字符数还剩多少,但是这是错的,因为在长度为 i 停留在当前节点 ///j 的字符串可能有多种,而这些字符串所拥有的剩余字符数是不一样的,不能单纯只用Lter[i][j][k]表示 ///实际上这只是我自己的理解,我没有打表跟踪过错误数据,你可以自己想想为什么这样子不行…… int newi = i + 1; int newj = ac.Node[j].Next[k]; dp[newi][newj] = max(dp[newi][newj], dp[i][j] + ac.Node[newj].cnt); for(int l=0; l<4; l++) Lter[newi][newj][l] = Lter[i][j][l]; Lter[newi][newj][k]--; } } } } }
那要如何定义DP状态呢?一般来说对于这样的数量和所在节点状态是关键点,所以我们可以DP[A][T][G][C][Node]前四维表示ATGC数量,最后一维表示当前状态停留在节点 Node ,但是这样子空间会爆炸,这时候就需要压缩一下状态,考虑压缩前四维,网上有很多利用进制的压缩,弱智的我有点不理解,所以还是用了普通的Hash,即利用一个四维数组 Hash[11][11][11][11] ( 每一个字母最多就是 10 个,所以这样开数组 ) ,然后只需要统计主串各个种类字符的数量,就能打出一个 Hash 表,将原本五维DP压成二维DP,DP[i][j] 表示各个字符数量状态为 i 且停留在 j 节点的最多包含模式串个数,则状态转移方程为 ( 一个节点状态能转到"ATGC"四个状态,那么以转到字符 ' A ' 为例 )
DP[ Hash[A+1][G][T][C] ][j] = max( DP[ Hash[A+1][G][T][C] ][j], DP[i][j] + Trie[j]['A'].cnt )
DP初始状态为 DP[0][0] = 0、DP[0~最大的Hash值数量][0~Trie图上的节点个数] = -1
#include<stdio.h> #include<string.h> #include<algorithm> #include<queue> using namespace std; const int Letter = 26; const int Max_Tot = 50 * 10 + 5; int mp[128]; int Hash[41][41][41][41]; struct Aho{ struct StateTable{ int Next[Letter]; int fail, cnt; }Node[Max_Tot]; int Size; queue<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[0].Next, 0, sizeof(Node[0].Next)); Node[0].fail = Node[0].cnt = 0; Size = 1; } inline void insert(char *s){ int now = 0; for(int i=0; s[i]; i++){ int idx = mp[s[i]]; if(!Node[now].Next[idx]){ memset(Node[Size].Next, 0, sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].cnt = 0; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].cnt++; } inline void BuildFail(){ Node[0].fail = 0; for(int i=0; i<Letter; i++){ if(Node[0].Next[i]){ Node[Node[0].Next[i]].fail = 0; que.push(Node[0].Next[i]); }else Node[0].Next[i] = 0;///必定指向根节点 } while(!que.empty()){ int top = que.front(); que.pop(); Node[top].cnt += Node[Node[top].fail].cnt; for(int i=0; i<Letter; i++){ int &v = Node[top].Next[i]; if(v){ que.push(v); Node[v].fail = Node[Node[top].fail].Next[i]; }else v = Node[Node[top].fail].Next[i]; } } } }ac; char S[44]; int dp[11*11*11*11+5][505]; int Solve() { int num[4]; memset(num, 0, sizeof(num)); for(int i=0; S[i]; i++) num[mp[S[i]]]++; int HashCnt = 0; for(int A=0; A<=num[0]; A++) for(int T=0; T<=num[1]; T++) for(int G=0; G<=num[2]; G++) for(int C=0; C<=num[3]; C++) Hash[A][T][G][C] = HashCnt++; for(int j=0; j<=ac.Size; j++) for(int i=0; i<=HashCnt; i++) dp[i][j] = -1; dp[0][0] = 0; for(int A=0; A<=num[0]; A++){ for(int T=0; T<=num[1]; T++){ for(int G=0; G<=num[2]; G++){ for(int C=0; C<=num[3]; C++){ for(int i=0; i<ac.Size; i++){ int j = Hash[A][T][G][C]; if(dp[j][i] >= 0){ for(int k=0; k<4; k++){ if(k==0 && A == num[0]) continue; if(k==1 && T == num[1]) continue; if(k==2 && G == num[2]) continue; if(k==3 && C == num[3]) continue; int a, t, g, c; a = (k==0), t = (k==1); g = (k==2), c = (k==3); dp[Hash[A+a][T+t][G+g][C+c]][ac.Node[i].Next[k]] = max(dp[Hash[A+a][T+t][G+g][C+c]][ac.Node[i].Next[k]], dp[j][i] + ac.Node[ac.Node[i].Next[k]].cnt); } } } } } } } int ans = 0; int MaxNum = Hash[num[0]][num[1]][num[2]][num[3]]; for(int i=0; i<ac.Size; i++) ans = max(ans, dp[MaxNum][i]); return ans; } int main(void) { int n, Case = 1; mp['A']=0, mp['T']=1; mp['G']=2, mp['C']=3; while(~scanf("%d", &n) && n){ ac.init(); for(int i=0; i<n; i++){ scanf("%s", S); ac.insert(S); }ac.BuildFail(); scanf("%s", S); printf("Case %d: %d\n", Case++, Solve()); } return 0; }