BZOJ1444: [Jsoi2009]有趣的游戏 矩阵求逆+AC自动机

题意:N个人,每个人有一个长度为L的字符串,字符都在前M个大写字母中,现开始连续地随机产生字符,每次产生某个字符的概率是固定的,当一个人的字符串被产生出来他就赢了,求每个人赢的概率。
N,M,L<=10
百度了一下没查着和我一样用矩阵求逆做的。。。好像精度要求不高,邻接矩阵自乘几次就能过?
算了。。。首先赢不了的人可以特判掉,所有人都赢不了就直接退出(我觉得这样应该可以防止矩阵不可逆),建一下AC自动机,就可以把邻接矩阵搞出来,注意单词结点没有任何出边(这题由于所有人单词长度都一样,不存在一个单词结点可能被另一个结点的fail指针指向的情况)
我们要求的每个人获胜的概率等于转移一次获胜的概率+转移两次获胜的概率+……+转移无穷次获胜的概率。设T为初始的邻接矩阵,X为每次转移后的矩阵之和,即:
X=T^1+T^2+T^3+⋯+T^∞
TX=T^2+T^3+⋯+T^∞
(1-T)X=T
所以用T乘以(1-T)的逆矩阵即可(1为单位矩阵),最后X[0][N]就是从0号节点(根)转移到N号结点的概率。
矩阵求逆的过程就是和高斯消元解线性方程组一样,不过未知数不是N个数字,而是N个行向量,然后把原矩阵放在左面,单位矩阵放在右面,左边原矩阵消成单位矩阵之后右边的单位矩阵就会变成原矩阵的逆矩阵。
话说我不会严格证明这题里的矩阵一定可逆。。。如果有路过的神犇明白的话欢迎赐教。

#include<cstdio>
#include<queue>
#include<cmath>
#include<algorithm>
#define gm 101
using namespace std;
int maxn;
int n,m,l;
inline void __swap(double *a,double *b)
{
    int tms=maxn;
    while(tms--)
    iter_swap(a++,b++);
}
struct martix
{
    double m[gm][gm];
    martix():m(){}
    martix(int):m()
    {
        for(int i=0;i<=maxn;++i)
        m[i][i]=1;
    }
    inline double* operator [] (size_t x) {return m[x];}
    inline const double* operator [] (size_t x) const {return m[x];}
    inline martix operator - (const martix& b) const
    {
        martix res;
        for(int i=0;i<=maxn;++i)
        for(int j=0;j<=maxn;++j)
        res[i][j]=m[i][j]-b[i][j];
        return res;
    }
    inline martix operator * (const martix &b) const
    {
        martix res;
        for(int i=0;i<=maxn;++i)
        for(int j=0;j<=maxn;++j)
        for(int k=0;k<=maxn;++k)
        res[i][j]+=m[i][k]*b[k][j];
        return res;
    }
    inline void inv()
    {
        martix res(1);
        for(int i=0;i<=maxn;++i)
        {
            for(int j=i;j<=maxn;++j)
            if(fabs(m[j][i])>1e-6)
            {
                 if(i!=j) __swap(m[i],m[j]),__swap(res[i],res[j]);
                 break;
            }
            for(int j=i+1;j<=maxn;++j)
            {
                double temp=m[j][i]/m[i][i];
                for(int k=i;k<=maxn;++k)
                m[j][k]-=m[i][k]*temp;
                for(int k=0;k<=maxn;++k)
                res[j][k]-=res[i][k]*temp;
            }
        }
        for(int i=maxn;i>=0;--i)
        {
            for(int j=0;j<=maxn;++j)
            res[i][j]/=m[i][i];
            for(int j=i-1;j>=0;--j)
            {
                for(int k=0;k<=maxn;++k)
                res[j][k]-=res[i][k]*m[j][i];
            }
        }
        *this=res;
    }
    inline martix operator / (martix &b) const
    {
        b.inv();
        return operator*(b);
    }
}t,u;
double ps[26];
struct node
{
    node *s[26],*fail;
    bool da;
    node():s(),fail(),da(){}
    inline void* operator new(size_t);
}pool[gm];
inline void* node::operator new(size_t)
{
    static int t=-1;
    return pool+ ++t;
}
struct acam
{
    node *rt;
    acam():rt(new node){rt->fail=rt;}
    inline void insert(const char *s,int& pos)
    {
        node *now=rt;
        while(*s)
        {
            node*& x=now->s[*s-'A'];
            if(!x) x=new node;
            now=x;
            ++s;
        }
        now->da=1;
        pos=now-pool;
    }
    inline void aho_corasick()
    {
        queue<node*> q;
        maxn=0;
        for(int i=0;i<m;++i)
        {
            node*& x=rt->s[i];
            if(!x) x=rt;
            else x->fail=rt,q.push(x);
        }
        while(!q.empty())
        {
            ++maxn;
            node *now=q.front();q.pop();
            for(int i=0;i<m;++i)
            {
                node*& x=now->s[i];
                if(!x) x=now->fail->s[i];
                else x->fail=now->fail->s[i],q.push(x);
            }
        }
    }
}ac;
int p,q,pos[11];
bool lose[11];
char s[11];
int main()
{
    scanf("%d%d%d",&n,&l,&m);
    for(int i=0;i<m;++i)
    {
        scanf("%d%d",&p,&q);
        ps[i]=double(p)/q;
    }
    for(int i=0;i<n;++i)
    {
        scanf("%s",s);
        for(int j=0;s[j];++j)
        if(fabs(ps[s[j]-'A'])<1e-6)
        {
            lose[i]=1;
            break;
        }
        if(lose[i]) continue;
        ac.insert(s,pos[i]);
    }
    bool skip=1;
    for(int i=0;i<n;++i)
    {
        if(!lose[i])
        {
            skip=0;
            break;
        }
    }
    if(skip) goto calc;
    ac.aho_corasick();
    for(int i=0;i<=maxn;++i)
    if(!pool[i].da)
    {
        for(int j=0;j<m;++j)
        t[i][pool[i].s[j]-pool]+=ps[j];
    }
    u=martix(1)-t;
    t=t/u;
    calc:
    for(int i=0;i<n;++i)
    {
        if(lose[i]) puts("0.00");
        else printf("%.2lf\n",t[0][pos[i]]);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值