BZOJ 2553: [BeiJing2011]禁忌(AC自动机+期望DP+矩阵快速幂)

传送吧

https://www.lydsy.com/JudgeOnline/problem.php?id=2553


思路

建出trie图。记f[i][j]为长度为i到节点j的期望。直接转移不行。

建出trie图的邻接矩阵,然后自乘len-1次转移。

具体就是如果当前点的儿子是禁忌点,就连向根(由儿子连长度多了1),否则连向儿子。这样就避免了重叠。而且有个显然的结论就是能走到禁忌点就走,肯定能取到最大值。

我们在每条边上放上概率,将值放在一个新建的点上,然后能走到禁忌点就连向它。不能放在根是显然的。转移就相当于i->k,k->j贡献i->j,就是在trie图上有方向的走(没方向考虑高消),然后统计答案。问的是从根走len步走到最终点的期望。

这道题告诉我们算期望可以先考虑概率,将值放在最后再乘。

long double是必要的,其他细节看代码。


代码

#include <bits/stdc++.h>
#define maxn 105

using namespace std;

int n, len, alp, cnt;
char x[maxn];

struct AC{
    AC *son[26], *fail;
    int ep, id;
    void Clear(){
        for(int i = 0; i < 26; i++)  son[i] = NULL;
        ep = 0;
        id = cnt;
    }
}Node[maxn], *Root, *q[maxn];

struct Mat{
    int r, c;
    long double A[maxn][maxn];
    void Clear(){
        memset(A, 0, sizeof(A));
    }
}f;

AC *NewTnode(){
    Node[cnt].Clear();
    return Node+cnt++;
}

void Insert(char *s){
    AC *now = Root;
    for(; *s != '\0'; s++){
        int pos = (*s) - 'a';
        if(!now->son[pos])  now->son[pos] = NewTnode();
        now = now->son[pos];
    }
    now->ep = 1;
}

void Build(){
    int hh = 0, tt = 0;
    q[hh] = Root;
    Root->fail = NULL;

    while(hh <= tt){
        AC *now = q[hh++];
        for(int i = 0; i < alp; i++){
            if(now->son[i]){
                q[++tt] = now->son[i];
                now->son[i]->fail = (now == Root) ? Root : now->fail->son[i];
                now->son[i]->ep |= now->son[i]->fail->ep;
            }
            else  now->son[i] = (now == Root) ? Root : now->fail->son[i];
        }
    }
}

Mat operator * (Mat X, Mat Y){
    Mat Z;
    Z.r = X.r;  Z.c = Y.c;
    Z.Clear();

    for(int i = 0; i <= X.r; i++)
        for(int j = 0; j <= Y.c; j++)
            for(int k = 0; k <= X.c; k++)
                Z.A[i][j] = (Z.A[i][j] + X.A[i][k] * Y.A[k][j]);
    return Z;
}

Mat Pow(Mat X){
    Mat Z = X;
    len --;
    while(len){
        if(len & 1)  Z = Z * X;
        X = X * X;
        len >>= 1;
    }
    return Z;
}


int main(){

    scanf("%d%d%d", &n, &len, &alp);

    Root = NewTnode();

    for(int i = 1; i <= n; i++){
        scanf("%s", x);
        Insert(x);
    }

    Build();

    f.r = f.c = cnt;
    f.Clear();

    f.A[cnt][cnt] = 1.0;
    long double possi = 1.0 / alp;

    for(int i = 0; i < cnt; i++){
        AC *now = Node+i;
        for(int j = 0; j < alp; j++){  
            if(now->son[j]->ep){
                f.A[i][0] += possi;
                f.A[i][cnt] += possi;
                continue;
            }
            f.A[i][now->son[j]->id] += possi;
        }
    }

    f = Pow(f);

    printf("%.7Lf\n", f.A[0][cnt]);

    return 0;
}

What a good thing we lose
多么幸运 我们错失彼此

What a bad thing we knew
何其不幸 我们曾经相知

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值