bzoj1444 [Jsoi2009]有趣的游戏(期望概率+AC自动机+高斯消元)

83 篇文章 0 订阅
53 篇文章 0 订阅

bzoj1444 [Jsoi2009]有趣的游戏

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=3881

题意:
这里写图片描述

数据范围
n , l, m≤ 10,0<=pi<=10,1<=qi<=10

题解:
Sengxian大佬的题解

一个好想又好写的思路是直接算T=∞时,不能出现某个序列的概率,
就是补全AC自动机那个无向图的邻接矩阵,把可以转移到该串尾节点及包含该串结尾的节点的边去掉,
剩下的边就是转移概率,虽然不能∞,但矩阵快速幂跑个几十万次也不会差太多了。

但是我们还是要想想如何求得准确的概率。

首先我们知道经过root的概率是1,但其他点既可以转移向root,也可以由root转移,这就没有办法处理。

考虑到我们只需要得到单词结尾节点的概率,而单词结尾节点不会再向外转移,
那么经过单词结尾节点的期望次数在数值上等于到达单词结尾节点的概率。

那么我们只需要求得经过每个点的期望次数即可。

假设AC自动机除去 root=0 r o o t = 0 s s 个节点,这样就可以构造方程了:

对于root的处理:
最初,我们知道在不考虑其他转移的情况下root必然经过一次,
ErootEiPi,root=1(i可以转移向root)
那么最初 矩阵 M0,0M0,s+1 M 0 , 0 与 M 0 , s + 1 都为1,
如果有点i可以通过P的概率转移向 root r o o t ,那么 M0,i=P M 0 , i − = P

对于其他点的处理:
最初我们并不知道每个点经过次数的期望,
EiEjPj,i=0 E i − ∑ E j ∗ P j , i = 0
移项,那么 Mi,i=1 M i , i = − 1 Mi,j=Pj,i M i , j = P j , i

于是这样构造矩阵,跑高斯消元解方程即可。
例如:

2 2 2
1 2
1 2
BA
BB

得到矩阵:

0.50 0.00 0.00 0.00 1.00
0.50 -1.00 0.00 0.00 0.00
0.00 0.50 -1.00 0.00 0.00
0.00 0.50 0.00 -1.00 0.00

最后,注意特判没有胜者的情况。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
const double EPS=1e-10;
const int N=10+5;
const int M=105;
int n,l,m,ch[M][26],tail=0,root=0,isw[M],fail[M],cnt=0,loc[N];
queue<int> Q;
double P[26],C[M][M];
char str[N];
int insert()
{
    int len=strlen(str); int tmp=root; bool flag=0;
    for(int i=0;i<len;i++)
    {
        int c=str[i]-'A'; if(P[c]<=EPS) flag=1;
        if(!ch[tmp][c]) ch[tmp][c]=++tail;
        tmp=ch[tmp][c];
    }
    isw[tmp]=1; if(flag) cnt++;
    return tmp;
}
void getfail()
{
    for(int i=0;i<m;i++) if(ch[root][i]) Q.push(ch[root][i]),fail[ch[root][i]]=root; else ch[root][i]=root;
    while(!Q.empty())
    {
        int top=Q.front(); Q.pop();
        for(int i=0;i<m;i++)
        if(!ch[top][i]) ch[top][i]=ch[fail[top]][i];
        else
        {
            int u=ch[top][i];
            fail[u]=ch[fail[top]][i];
            isw[u]|=isw[fail[u]];
            Q.push(u);
        }
    }
}
void gauss()
{
    for(int i=0;i<=tail;i++)
    {
        int pos=-1;
        for(int j=i;j<=tail;j++)
        if(fabs(C[j][i])>EPS){pos=j;break;}
        if(pos==-1) continue;
        for(int j=0;j<=tail+1;j++) swap(C[i][j],C[pos][j]);
        for(int j=0;j<=tail;j++)
        {
            if(i==j||fabs(C[j][i])<=EPS) continue;
            double r=C[j][i]/C[i][i];
            for(int k=0;k<=tail+1;k++) C[j][k]-=C[i][k]*r;
        }
    }
}
int main()
{
    scanf("%d%d%d",&n,&l,&m);
    for(int i=0;i<m;i++) 
    {
        int p,q; scanf("%d%d",&p,&q);
        P[i]=(double)p/q;
    }
    for(int i=1;i<=n;i++) scanf("%s",str),loc[i]=insert();
    if(cnt==n) {for(int i=1;i<=n;i++) printf("0.00\n"); return 0;}
    getfail();
    for(int i=0;i<=tail+1;i++) for(int j=0;j<=tail+1;j++)
    if(i==j&&i!=tail+1) C[i][j]=-1; else C[i][j]=0.0;
    C[0][0]=1; C[0][tail+1]=1;
    for(int i=0;i<=tail;i++)
    {
        if(isw[i]) continue;
        for(int c=0;c<m;c++)
        {
            int j=ch[i][c];
            if(j==root) C[root][i]-=P[c];
            else C[j][i]+=P[c];
        }
    }
    gauss();
    for(int i=1;i<=n;i++)
    {
        double ans=C[loc[i]][tail+1]/C[loc[i]][loc[i]];
        if(fabs(ans)<=EPS) printf("0.00\n");
        else printf("%0.2lf\n",ans);
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值