Substring UVA - 11468 AC自动机加记忆化

本文探讨了如何使用AC自动机解决一个复杂的问题:计算一个随机生成的字符串不包含特定模板串的概率。通过构建AC自动机并结合记忆化搜索,文章详细解释了算法的实现过程。

OJ:https://vjudge.net/problem/UVA-11468

简单翻译:

给出一些字符和各自被选择的概率,随机选择L次之后,得到一个长度为L的随机字符串S,每次选择互相独立。给出K个模板串,计算S不包含任何一个模板串的概率,既S的任意一个子串都与K个模板串的任何一个相同

 

分析:

计算不包含模板串的概率,也就是要计算我们每次选择不会出现于模式串相同的子串的那些选择的概率之和。也就是全概率

对于每一次的随机选择,我们要保证的是,保证在选择当前字符之后,不会出现K个模式串中的那些串。怎么去保证这个过程呢?

这就要利用到AC自动机了,AC自动机是让我们来匹配字符串的,我们也可以使用其来确保不匹配。我们的每一次选择都相当于在AC自动机上移动了一步,也就是在AC自动机上的路径进行移动。

一个重要的点就是,我们不能在AC自动机上走到一个模版串的结束位置。

我们首先构造出AC自动机,当然这个AC自动机与我们传统的AC自动机来说是有一些不同的,稍后再说不同。

然后对于我们每一次的随机选择,都相当于在AC自动机中随机的走一步。我们每一次选择有给出的字符这些选择,对于每一种选择来说,有一些选择可能在AC自动机上可以继续往下走,但是有一些选择就会导致与当前AC自动机上的当前位置的下的任意一个点都不同,这时候当前路径就不能往下走了,我们就要往当前节点的fail节点走,因为在当前走的路径上出现了不匹配的情况,对于当前路径上的模式串,肯定就会不匹配,但是对于其他路径来说就不一定了,因为适配节点上匹配了部分相同的字符,所以就要走到其失配节点上,去保证之前走过的一些点继续走也不会到达一个模板串的结束位置。

另外一点就是,我们在当前路径上走,在当前路径上没有出现模板串的结束位置,但是这并不是说,在其他路径上就不会出现结束位置。比如这样两个串:abcd,bc。我们在abcd路径上走的时候,走到c字符时其实就已经在其他路径上出现了匹配的模式串。所以我们要保证对于我们在当前路径上走的每一个节点,我们要保证在该节点的失配节点上也不会出现模板串的结束位置。

满足了上面两点就可以了

 

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 450
#define N 80
#define L 101

typedef struct{
    int val[MAX];
    int word[MAX][80];
    int fail[MAX];
    int size;
}Trie;

int idx(char c);
void insert(char *s);
double dfs(int l,int index);
void getFail();
void push(int data);
int pop();
int isEmpty();
Trie trie;

char pattern[21][21];
double p[80];
double vis[L][MAX];
double d[L][MAX];
int queue[MAX],size,pre,next;

int main()
{
    int t;
    scanf("%d",&t);
    int cs = 1;
    for(;t>0;t--){
        trie.size = 1,size=0,pre=0,next=0;
        memset(trie.word[0],0,sizeof(trie.word[0]));
        memset(trie.val,0,sizeof(trie.val));
        memset(vis,0.0,sizeof(vis));
        memset(p,0.0,sizeof(p));
        int k,n,l;
        scanf("%d",&k);
        for(int i=0;i<k;i++){
            scanf("%s",pattern[i]);
            insert(pattern[i]);
        }
        getFail();
        scanf("%d",&n);
        for(int i=0;i<n;i++){
            getchar();
            int c;
            scanf("%c",&c);
            scanf("%lf",&p[idx(c)]);
        }
        scanf("%d",&l);
        printf("Case #%d: %lf\n",cs++,dfs(l,0));
    }
}

// 记忆化搜索的过程
double dfs(int l,int index){
    if(!l){
        return 1.0;
    }
    if(vis[l][index]){
        return d[l][index];
    }
    vis[l][index] = 1;
    double res = 0.0;
    for(int i=0;i<N;i++){
        int nextIndex = trie.word[index][i];
        // val的值大于0,说明当前节点不能走,因为我们的处理是,val大于0的地方会出现模板串的结束位置
        if(p[i] && !trie.val[nextIndex]){
            // p[i]表示的是选择当前i位置的字符的概率
            res += p[i] * dfs(l-1,nextIndex);
        }
    }
    d[l][index] = res;
    return res;
}

void getFail(){
    for(int i=0;i<80;i++){
        int index = trie.word[0][i];
        if(index){
            push(index);
            trie.fail[index] = 0;
        }
    }
    while(!isEmpty()){
        int parentNode = pop();
        for(int i=0;i<80;i++){
            int curNode = trie.word[parentNode][i];
            if(!curNode){
                //curNode = 0,也就是说当前的parentNode往下走不存在i字符,根据我们之前的分析
                //虽然在当前路径上当前字符走不下去了,但是当前路径上此前走过的字符,可能在其他路径上也存在,所以我们这里将当前路径的下一位置,指向了其适配节点上的那个位置,去防止在另外一个节点上出现匹配的模板串
                trie.word[parentNode][i] = trie.word[trie.fail[parentNode]][i];
                continue;
            }
            push(curNode);
            int pFail = trie.fail[parentNode];
            while(pFail && !trie.word[pFail][i]){
                pFail = trie.fail[pFail];
            }
            trie.fail[curNode] = trie.word[pFail][i];
            // 当前节点上有没有模板串的结尾,还要看起适配路径上有没有模板窜的结尾。有的话,当前节点也不能走
            trie.val[curNode] |= trie.val[trie.fail[curNode]];
        }
    }
}

void push(int data){
    size++;
    queue[next++] = data;
    if(next == MAX){
        next = 0;
    }
}

int pop(){
    size--;
    int res = queue[pre++];
    if(pre == MAX){
        pre = 0;
    }
    return res;
}

int isEmpty(){
    return size == 0;
}

void insert(char *s){
    int curNode = 0,len = strlen(s);
    for(int i=0;i<len;i++){
        int index = idx(s[i]);
        if(!trie.word[curNode][index]){
            memset(trie.word[trie.size],0,sizeof(trie.word[0]));
            trie.val[trie.size] = 0;
            trie.word[curNode][index] = trie.size++;
        }
        curNode = trie.word[curNode][index];
    }
    // 注意,结束位置一定要标记,代表了禁止选择的字符。
    trie.val[curNode] = 1;
}

int idx(char c){
    return c - '0';
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值