bzoj2553: [BeiJing2011]禁忌 AC自动机+矩阵乘法优化Dp

bzoj2553: [BeiJing2011]禁忌

Description

   Magic Land上的人们总是提起那个传说:他们的祖先John在那个东方岛屿帮助Koishi与其姐姐Satori最终战平。而后,Koishi恢复了读心的能力……

如今,在John已经成为传说的时代,再次造访那座岛屿的人们却发现Koishi遇到了新麻烦。
这次她遇到了Flandre Scarlet——她拥有可以使用禁忌魔法而不会受到伤害的能力。
为了说明什么是禁忌魔法及其伤害,引入以下概念:
1.字母集A上的每个非空字符串对应了一个魔法。
其中A是包含了前alphabet个小写字母的集合。
2.有一个集合T,包含了N个字母集A上的字符串
T中的每一串称为一个禁忌串(Taboo string)
3.一个魔法,或等价地,其对应的串s因为包含禁忌而对使用者造成的伤害按以下方式确定:
把s分割成若干段,考虑其中是禁忌串的段的数目,不同的分割可能会有不同的数目,其最大值就是这个伤害。

由于拥有了读心的能力,Koishi总是随机地使用Flandre Scarlet的魔法,可以确定的是,她的魔法正好对应字母集A上所有长度为len的串。
但是,Flandre Scarlet所使用的一些魔法是带有禁忌的,由于其自身特性,她可以使用禁忌魔法而不受到伤害,而Koishi就不同了。可怜的Koishi每一次使用对方的魔法都面临着受到禁忌伤害的威胁。

   你现在需要计算的是如果Koishi使用对方的每一个魔法的概率是均等的,那么每一次随机使用魔法所受到的禁忌伤害的期望值是多少。

Input

第一行包含三个正整数N、len、alphabet。
接下来N行,每行包含一个串Ti,表示禁忌串。

Output

一个非负实数,表示所受到禁忌伤害的期望值。

Sample Input

2 4 2
aa
abb

Sample Output

0.75
【样例1解释】
一共有2^4 = 16种不同的魔法。
需要注意的是“aabb”的禁忌伤害是1而不是2。

HINT

100%的数据中N ≤ 5,len ≤109,1 ≤ alphabet ≤ 26。
在所有数据中,有不少于40%的数据中:N = 1。
数据保证每个串Ti的长度不超过15,并且不是空串。
数据保证每个Ti均仅含有前alphabet个小写字母。
数据保证集合T中没有相同的元素,即对任意不同的i和j,有Ti≠Tj。
【评分方法】
对于每一组数据,如果没有得到正确的输出(TLE、MLE、RTE、输出格式错误等)得0分。
否则:设你的输出是YourAns,标准输出是StdAns:
记MaxEPS = max(1.0 , StdAns)×10-6
如果|YourAns – StdAns| ≤ MaxEPS则得10分,否则得0分。
即:你的答案需要保证相对误差或绝对误差不超过10-6。

分析

一道经典的AC自动机加矩阵乘法优化Dp的题。
一句话题意:等概率生成字母的定长随机串,求其包含最多个模板串个数的期望。
首先考虑如果这个随机串已经生成好了,我们怎么暴力去搞这个问题。
其实这是一个很明显的贪心,就是走到匹配就从头来,贡献++即可。
这个贪心必定是在AC自动机上进行的。
很套路的就是,在AC自动机上建图,枚举所有转移。
如果转移 t r a n s ( u , v ) v 状态含有模板串,我们强行令 t r a n s ( u , v ) = t r a n s ( u , S )
然后我们把边权变成这个转移发生的概率。
这样子可以模拟出全过程。
注意这里转移的是概率,网上有些题解写的是期望,其实是谬误。
因为 P ( i , j ) = P ( i , k ) P ( k , j )
这个时候如何统计答案?
不难发现,所有答案都集中在 t r a n s ( u , v ) = t r a n s ( u , s t ) 这一步转移上。
这一步转移对答案的贡献是1,所以期望是 P t r a n s ( u , v ) 1
这个时候新建虚拟节点 T ,只要在 t r a n s ( u , v ) = t r a n s ( u , S ) 时向 T 连边 P t r a n s ( u , v ) 1 ,即可得到每步的答案为 w ( S , T )
但是我们统计的是全过程的答案,要保留下之前的答案怎么办?
连边 w ( T , T ) = 1 即可。
再次强调一下,所有和 T 关联的边上的边权代表的是期望,其余自动机中的边上的边权代表的是概率

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 105;
long double A[N][N], B[N][N], C[N][N], tp[N][N];
int ch[N][26], q[N], f[N], sz, p, n, k;
bool val[N];
void Ins(int &u, int c) {u = ch[u][c] ? ch[u][c] : ch[u][c] = ++sz;}
void read() {
    char ch = getchar(); int u = 0;
    for(;ch < 'a' || ch > 'z'; ch = getchar()) ;
    for(;ch >= 'a' && ch <= 'z'; ch = getchar()) Ins(u, ch - 'a');
    val[u] = true;
}
void Build() {
    int L = 0, R = 0;
    for(int i = 0;i < p; ++i) if(ch[0][i]) q[++R] = ch[0][i];
    while(L < R) {
        int u = q[++L];
        for(int i = 0;i < p; ++i) {
            int &v = ch[u][i];
            if(!v) {v = ch[f[u]][i]; continue;}
            f[v] = ch[f[u]][i]; val[v] |= val[f[v]];
            q[++R] = v;
        }
    }
}
void Init(long double A[N][N]) {
    for(int i = 0;i <= sz; ++i)
        for(int j = 0;j <= sz; ++j)
            A[i][j] = 0;
}
void Mem(long double A[N][N], long double B[N][N]) {
    for(int i = 0;i <= sz; ++i)
        for(int j = 0;j <= sz; ++j)
            A[i][j] = B[i][j];
}
void Mul(long double C[N][N], long double A[N][N], long double B[N][N]) {
    Init(tp);
    for(int i = 0;i <= sz; ++i)
        for(int j = 0;j <= sz; ++j)
            for(int k = 0;k <= sz; ++k)
                tp[i][j] += A[i][k] * B[k][j];
    Mem(C, tp);
}
int main() {
    scanf("%d%d%d", &n, &k, &p);
    while(n--) read();
    Build(); ++sz; long double v = 1.0 / p;
    for(int u = 0;u < sz; ++u)
        for(int i = 0;i < p; ++i)
            if(val[ch[u][i]]) {A[u][0] += v; A[u][sz] += v;}
            else A[u][ch[u][i]] += v;
    A[sz][sz] = 1; Mem(B, A); --k;
    for(; k; Mul(B, B, B), k >>= 1) if(k & 1) Mul(A, A, B);
    printf("%.10lf\n", (double)A[0][sz]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/lvzelong2014/article/details/80025054