UVALive 3490 (LA 3940) || ZOJ 2619 Generator AC自动机(或KMP) + 整数高斯消元 + 数学期望

题目大意:

就是现在一个字符串生成器每次随机扔出前n(n <= 26)个大写英语字母的一个

将产生的字符连接起来成为其生成的字符串,如果它产生的字符串中有连续的一段出现了给定的禁止串,则生成停止

求停止时已经生成的字符串长度的期望


大致思路:

一开始果断用了AC自动机,后来发现KMP也就足够了

这个题建立方程组之后用Gauss消元不能用double的,容易产生误差...(因为误差跪了好多发之后改成整数版)

状态转移方程和细节见代码注释..


代码如下:

Result  :  Accepted     Memory  :  276 KB     Time  :  0 ms

/*
 * Author: Gatevin
 * Created Time:  2015/2/12 18:23:00
 * File Name: Mononobe_Mitsuki.cpp
 */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const double eps(1e-8);
typedef long long lint;

int n;
char in[15];

/*
 * 首先建立AC自动机得到状态转移图
 * 用E[i]表示在转移图中的点i处到达目标状态需要的期望步数, 则E[0]即为解(root == 0)
 * 由于只有一个串记其结尾位置为AC自动机中的点L - 1长度为L的话
 * 显然E[L - 1] = 0 (L为AC自动机上的总点数)
 * 对于0 <= i < L - 1有转移方程E[i] = ∑(E[next[i][j]] + 1) / n , (0 <= j < n)
 * L - 1 <= 12可以使用高斯消元求解复杂度O(L^3) AC自动机复杂度O(L)
 * 由于此题有误差,使用double的高斯消元求得E[0]后转long long误差太大不可行
 * 需要用整数型的高斯消元
 */
struct Trie
{
    int next[15][26], fail[15];
    bool end[15];
    int L, root;
    int var, equ;
    lint a[15][15], x[15];
    int newnode()
    {
        for(int i = 0; i < n; i++)
            next[L][i] = -1;
        end[L++] = 0;
        return L - 1;
    }
    void init()
    {
        L = 0, equ = 0, var = 0;
        root = newnode();
        return;
    }
    void insert(char *s)
    {
        int now = root;
        for(; *s; s++)
        {
            if(next[now][*s - 'A'] == -1)
                next[now][*s - 'A'] = newnode();
            now = next[now][*s - 'A'];
        }
        end[now] = 1;
        return;
    }
    void build()
    {
        fail[root] = root;
        queue  Q;
        Q.push(root);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            for(int i = 0; i < n; i++)
                if(next[now][i] == -1)
                    next[now][i] = now == root ? root : next[fail[now]][i];
                else
                {
                    fail[next[now][i]] = now == root ? root : next[fail[now]][i];
                    Q.push(next[now][i]);
                }
        }
        return;
    }
    void getEquationSystem()
    {
        memset(a, 0, sizeof(a));
        var = L;
        for(int i = 0; i < L - 1; i++)
        {
            a[equ][i] += n;
            for(int j = 0; j < n; j++)
                a[equ][next[i][j]] -= 1;
            x[equ++] = n;
        }
        a[equ][L - 1] = 1;
        x[equ++] = 0;
        return;
    }
    lint Gauss()//表示用double版本的高斯消元求解,最后还原成整数的方法因为误差跪掉了....
    {
        for(int i = 0; i < equ; i++)
        {
            int r = i;
            while(r < equ && !a[r][i]) r++;
            if(r != i)//找到第r列不是0的之后交换至r行
            {
                for(int j = 0; j < var; j++) swap(a[r][j], a[i][j]);
                swap(x[r], x[i]);
            }
            for(int k = i + 1; k < equ; k++)
                if(a[k][i])//将下面所有行的第i列变成0
                {
                    lint tmp = a[k][i];//两组互相乘上对面的第i列的数作差即可
                    for(int j = i; j < var; j++) a[k][j] = a[k][j]*a[i][i] - tmp*a[i][j];
                    x[k] = x[k]*a[i][i] - tmp*x[i];
                }
        }
        //对剩下的三角矩阵递推求解
        for(int i = equ - 1; i >= 0; i--)
        {
            for(int j = i + 1; j < var; j++)
                x[i] -= x[j]*a[i][j];
            x[i] /= a[i][i];
        }
        return x[0];
    }
};

Trie AC;

int main()
{
    int T;
    scanf("%d", &T);
    for(int cas = 1; cas <= T; cas++)
    {
        scanf("%d", &n);
        AC.init();
        scanf("%s", in);
        AC.insert(in);
        AC.build();
        AC.getEquationSystem();
        printf("Case %d:\n%lld\n", cas, AC.Gauss());
        if(cas != T) printf("\n");
    }
    return 0;
}


你可能感兴趣的:(ZOJ,UVALive,ACM_Probability)