码迷,mamicode.com
首页 > 其他好文 > 详细

woj1572 Cyy and Fzz KMP / AC自动机 + DP

时间:2015-08-18 22:42:45      阅读:226      评论:0      收藏:0      [点我收藏+]

标签:

题目: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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!