2 4 ATG 4 TGC -3 1 6 TGC 4 4 1 A -1 T -2 G -3 C -4
4 4 No Rabbit after 2012!Hintcase 1:we can find a rabbit whose gene string is ATGG(4), or ATGA(4) etc. case 2:we can find a rabbit whose gene string is TGCTGC(4), or TGCCCC(4) etc. case 3:any gene string whose length is 1 has a negative W.
题意:给出一些模式串,每个串有一定的价值,现在要构造一个长度为M的串,问最大的价值为多少,每个模式串最多统计一次。
首先涉及到了字符串的匹配,要用到ac自动机,其次,求最大价值那么就要用状态压缩dp来解决了。网上的各种博客并没有一个把这一题讲解的很清晰的。。
首先来说ac自动机吧,建立ac自动机我用的二维数组模拟,用val[]数组来表示每一个子串的结束(可是存的数并不是1,2,3,4,5……这些,因为我们后边要用到状态压缩dp,那么这里我用2^i,也就是代码里的1<<i来表示,为何要这样表示?我们用位运算来模拟总共这n个子串如果存在就是1,不存在就是0,那么我们现在每个子串的结尾用2^i表示,我们想想,把2^i转化为2进制之后,是不是恰好都是只有1个1?这样来精确的表示存在第i个字串,这里跟后面的状态压缩dp有非常大的关联)
然后是建立自动机的过程,与正常的建立差不多,只不过也是val数组有变动,因为相当于在一个子串结束了恰好接上另一个子串的时候,难道val[]不变吗?如果不变那么我们建立ac自动机就没意义了。试想我们用val[ ch[p][i] ] | = val[ f [ ch [p][i] ] ];(f[]数组是失配指针数组),为何要用或?因为一个子串的尾节点的val值一定是1到1<<n之间的化为二进制只有一位为1的某数,现在它连接了另一个单词,那么在二进制里面又加上了另一个单词,不过val里边却只有一个1,这样我们用或的位运算来恰好接上另一个1来表示两个子串的相连。
然后是dp部分,我们用dp[i][j][k]表示状态,就是要构造长度为i的到了ac自动机里第j个节点的对应状态为k的(当然k一定要用二进制的眼光来看,0表示某个子串不存在,1表示存在)状态是否成立,如果成立,那么让他等于1,否则继续。
if(dp[(i+1)&1][j][hh]) dp[i&1][ch[j][k]][hh|val[ch[j][k]]]=1;
这是dp部分的精华,因为上一个状态成立的之后,代表下一个状态能推出来的状态也必然成立,推出来的val[ch[j][k]]就是我介绍为什么要按上面的方式存储val的原因,它记录了某一个节点包含多少个子串的全部信息(当然是转化为二进制之后,为1代表有,0代表没有咯)
当然,由于空间不能开爆内存,所以采取滚动数组的方式,将dp[i][j][k]的i只开了2,,相当于模拟,同时大幅减小了内存。
#include <iostream> #include <stdio.h> #include <stdlib.h> #include<string.h> #include<algorithm> #include<math.h> #include<queue> using namespace std; typedef long long ll; const int CH =4,NODE = 1005; int m,n; int idx(char x) { if(x=='A')return 0; else if(x=='C')return 1; else if(x=='G')return 2; return 3; } int ch[NODE][CH],f[NODE],val[NODE],sz,v[NODE]; int node() { memset(ch[sz],0,sizeof(ch[sz])); val[sz]=0; return sz++; } void init() { sz=0; node(); memset(f,0,sizeof(f)); } void ins(char *s,int i) { int u=0; for(; *s; s++) { int c=idx(*s); if(!ch[u][c]) ch[u][c]=node(); u=ch[u][c]; } val[u]=1<<i; } int queu[NODE]; void getfail() { int l=0,r=0; queu[r++]=0; while(l<r) { int p=queu[l++]; for(int i=0; i<4; i++) { if(ch[p][i]==0) ch[p][i]=ch[f[p]][i]; else { int q=ch[p][i]; if(p) f[q]=ch[f[p]][i]; val[q]|=val[f[q]]; queu[r++]=q; } } } } bool dp[2][1005][1<<10]; int get(int x) { int ans=0; for(int i=0; i<(1<<n); i++) if(x&(1<<i))//这里的位运算也非常巧妙 ans+=v[i]; return ans; } void solve() { memset(dp,0,sizeof(dp)); 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<=sz; j++) { for(int k=0; k<4; k++) { for(int hh=0; hh<(1<<n); hh++) if(dp[(i+1)&1][j][hh]) dp[i&1][ch[j][k]][hh|val[ch[j][k]]]=1; } } } int ans=-999999999; for(int i=0; i<=sz; i++) for(int j=0; j<(1<<n); j++) if(dp[m&1][i][j]) ans=max(ans,get(j)); if(ans<0) printf("No Rabbit after 2012!\n"); else printf("%d\n",ans); } int main() { while(~scanf("%d%d",&n,&m)) { init(); for(int i=0; i<n; i++) { char ss[105]; scanf("%s%d",ss,&v[i]); ins(ss,i); } getfail(); solve(); } return 0; }