Uva11468——ac自动机,dp,记忆化搜索

本文介绍如何利用AC自动机结合记忆化搜索解决特定字符串匹配问题,通过构建AC自动机来高效查找模板串,并使用记忆化搜索避免重复计算,最终求得目标字符串不包含任何模板串的概率。

题意:给出一些字符和各自对应的选择概率,随机选择L次后将得到一个长度为L的字符串,同时给出一些模版串,计算该字符串不包含任何一个模版串的概率。

可以把随即选取字符看在ac自动机的节点和节点之间移动,所经过的边即代表所选的字符。同时ac自动机的失配指针和单词节点标记则为选取字符生成的字符串不包含模版串提供了方法:可以证明,若在移动中经过了一个节点,且该节点为单词节点或该节点失配指针路径上有单词节点,则该移动所生成的字符串一定包含了模版串。可以对用模版串形成的ac自动机进行深搜,同时避开上述节点,同时为了避免大量重复计算(dp(i, j)代表目前在节点i,且还需要走j步;可以看出在递归搜索中对同一个dp(i, j)肯定进行了重复的计算),可以用记忆化搜索,用数组将子问题的解保存起来,从而将时间从指数级别降为多项式级别。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;

const int maxn = 1000;
const int sigma_size = 62;
int t, k, n, l;
double proba[sigma_size];
double dp[maxn][105];

struct ac_machine
{
    int ch[maxn][sigma_size];
    int val[maxn];
    int f[maxn];
    int last[maxn];
    int sz;
    void init(){ sz = 1; memset(ch, 0, sizeof(ch)); memset(val, 0, sizeof(val)); memset(f, 0, sizeof(f)); memset(last, 0, sizeof(last)); }
    int idx(char c)
    {
        if(c >= 'a' && c <= 'z') return c - 'a';
        else if(c >= 'A' && c <= 'Z') return c - 'A' + 26;
        else return c - '0' + 52;
    }

    void insert(char * s, int v)
    {
        int u = 0;
        for(int i = 0; s[i]; ++i)
        {
            int c = idx(s[i]);
            if(!ch[u][c])
            {
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] = v;
    }

    void getFail()
    {
        queue<int> q;
        f[0] = 0;
        for(int c = 0; c < sigma_size; ++c)
        {
            int u = ch[0][c];
            if(u) { f[u] = 0; q.push(u); last[u] = 0; }
            else ch[0][c] = 0;
        }
        while(!q.empty())
        {
            int r = q.front(); q.pop();
            for(int c = 0; c < sigma_size; ++c)
            {
                int u = ch[r][c];
                if(u)
                {
                    q.push(u);
                    int p = f[r];
                    while(p && ch[p][c] == 0) p = f[p];
                    f[u] = ch[p][c]; last[u] = val[f[u]] ? f[u] : last[f[u]];
                }
                else ch[r][c] = ch[f[r]][c];
            }
        }
    }

    double dfs(int u, int ll)
    {
        if(dp[u][ll] != -1) return dp[u][ll];
        if(ll == 0) return 1;
        double res = 0;
        for(int i = 0; i < sigma_size; ++i)
        {
            int v = ch[u][i];
            if(proba[i] == 0 || val[v] || last[v]) continue;
            res += proba[i] * dfs(v, ll - 1);
        }
        return dp[u][ll] = res;
    }

}ac;

int main()
{
    freopen("in.txt", "r",stdin);
    scanf("%d", &t);
    int cc = 1;
    while(t--)
    {
        ac.init(); for(int i = 0; i < sigma_size; ++i) proba[i] = 0;
        for(int i = 0; i < maxn; ++i) for(int j = 0; j < 105; ++j) dp[i][j] = -1;
        scanf("%d", &k);
        for(int i = 0; i < k; ++i)
        {
            char str[100];
            scanf("%s", str);
            ac.insert(str, 1);
        }
        scanf("%d", &n);
        for(int i = 0; i < n; ++i)
        {
            getchar();
            char c;
            scanf("%c", &c);
            scanf("%lf", &proba[ac.idx(c)]);
        }
        scanf("%d", &l);
        ac.getFail();
        printf("Case #%d: %.6lf\n", cc++, ac.dfs(0, l));
    }
    return 0;
}

/*
2
1
a
2
a 0.5
b 0.5
2

2
ab
ab
2
a 0.2
b 0.8
2

Case #1: 0.250000
Case #2: 0.840000
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值