标签:
题目:http://acm.whu.edu.cn/land/problem/detail?problem_id=1572
题意: 有n个目标串,长度均小于15,(n<=8),现在随机写一个长度为L的字符串,问写下的这个字符串包含目标串的期望的个数。
比赛的时候还以为是水题,其实是自己太水。这种题一般是AC自动机的中等题,本题也可以用KMP做,结合状压dp。
方法一:AC自动机
建完Trie树后,就是跑一遍dp,注意单词节点要 |=(1<<val),会有重的字符串。
dp过程: 用 dp[i][j][k] 表示在自动机节点i,当前剩余j步,已包含状态为k(k为二进制数)的概率。
转移就是 枚举下一个字符,由当前i点会沿着next数组走向now= next[i][ch]
dp[now][j-1][ k|end[now] ] += dp[i][j][k] * (1.0/26)
最后统计就可以了。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> using namespace std; const int MAX_N = 105; const int ALP = 26; const int N = 15; struct Trie{ int next[MAX_N][ALP] , fail[MAX_N] , end[MAX_N]; int root , L ; int newnode(){ for(int i=0;i<ALP;i++) next[L][i] = -1; end[L++] = 0; return L-1; } void init(){ memset(end,0,sizeof(end)); L = 0; root = newnode(); } void insert(char* buf , int id){ int now = root; int len = strlen(buf); for(int i=0;i<len;i++){ int ch = buf[i]-'a'; if(next[now][ch]==-1) next[now][ch] = newnode(); now = next[now][ch]; } end[now] |= 1<<(id-1); } void build(){ queue<int> Q; fail[root] = root; for(int i=0;i<ALP;i++) if(next[root][i]==-1) next[root][i] = root; else{ fail[next[root][i]] = root; Q.push(next[root][i]); } while(!Q.empty()){ int now = Q.front(); Q.pop(); for(int i=0;i<ALP;i++){ if(next[now][i]==-1) next[now][i] = next[fail[now]][i]; else{ fail[next[now][i]] = next[fail[now]][i]; Q.push(next[now][i]); } } } } double dp[MAX_N][N][(1<<8)+5]; double get_ans(int n,int step){ for(int i=step;i>=0;i--) for(int j=0;j<L;j++) for(int k=0;k<(1<<n);k++) dp[j][i][k] = 0; dp[0][step][0] = 1.0; for(int i=step;i>=1;i--) for(int j=0;j<L;j++) for(int k=0;k<(1<<n);k++)if(dp[j][i][k]>0) for(int p=0;p<26;p++){ int now = next[j][p]; int state = 0; int tmp = now; while(tmp!=root){ state |= end[tmp]; tmp = fail[tmp]; } dp[now][i-1][k|state] += dp[j][i][k]*(1.0/26); } double ret = 0; for(int i=0;i<L;i++) for(int k=1;k<(1<<n);k++)if(dp[i][0][k]>0){ int ep = 0; for(int p=0;p<n;p++) if(k&(1<<p)) ep++; // printf("node %d state %d num %d p=%f\n",i,k,ep,dp[i][0][k]); ret += ep*dp[i][0][k]; } return ret; } }ac; char s[15]; int main(){ int T,n,L; cin >> T; while(T--){ scanf("%d%d",&n,&L); ac.init(); for(int i=1;i<=n;i++){ scanf("%s",s); ac.insert(s,i); } ac.build(); // printf("numL = %d n L %d %d\n",ac.L,n,L); double ans = ac.get_ans(n,L); printf("%.6f\n",ans); } return 0; }
-----------------------------------------------------
方法二:KMP
由于每个目标串之间是独立的,所以可以单独统计每个目标串出现的期望,累加就可以。
那么对于单个串只需得到 不能包含它的概率就可以了 ,它的贡献就是1-p。
同样也是dp: dp[i][j]表示匹配到第i位(i未匹配成功)还剩余j步,不能包含此串的概率。
转移就是 枚举下一个字符,沿fail函数走到最大匹配位置的下一位置now
dp[now][j] += dp[i][j] * (1.0/26)
对于每一个目标串统计贡献和。
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAX_N = 20; struct KMP{ char P[MAX_N]; int fail[MAX_N],len; void get_fail(){ len = strlen(P); fail[0] = fail[1] = 0; for(int i=1;i<len;i++){ int j = fail[i]; while(j && P[j]!=P[i]) j = fail[j]; fail[i+1] = P[j]==P[i] ? j+1 : 0; } } double dp[MAX_N][15]; double get_ans(int L){ memset(dp,0,sizeof(dp)); dp[0][L] = 1.0; for(int i=L;i>=1;i--) for(int j=0;j<len;j++){ for(int k=0;k<26;k++){ if(j==len-1 && P[len-1]=='a'+k) continue; int now = j; if(P[j]=='a'+k) now = j+1; else{ while(now && P[now]!='a'+k) now = fail[now]; if(P[now]=='a'+k) now++; } dp[now][i-1] += dp[j][i]*(1.0/26); } } double ans = 1.0; for(int i=0;i<len;i++) ans -= dp[i][0]; return ans; } }km; int main(){ int T,n,L; cin >> T; while(T--){ scanf("%d%d",&n,&L); double ans = 0; for(int i=1;i<=n;i++){ scanf("%s",km.P); km.get_fail(); ans += km.get_ans(L); } printf("%.6f\n",ans); } return 0; }
通过这道题还是更加深入的理解了KMP 和AC自动机。
版权声明:本文为博主原创文章,未经博主允许不得转载。
woj1572 Cyy and Fzz KMP / AC自动机 + DP
标签:
原文地址:http://blog.csdn.net/alpc_wt/article/details/47760769