1. 程式人生 > >bzoj1444 [Jsoi2009]有趣的遊戲(期望概率+AC自動機+高斯消元)

bzoj1444 [Jsoi2009]有趣的遊戲(期望概率+AC自動機+高斯消元)

bzoj1444 [Jsoi2009]有趣的遊戲

題意:
這裡寫圖片描述

資料範圍
n , l, m≤ 10,0<=pi<=10,1<=qi<=10

一個好想又好寫的思路是直接算T=∞時,不能出現某個序列的概率,
就是補全AC自動機那個無向圖的鄰接矩陣,把可以轉移到該串尾節點及包含該串結尾的節點的邊去掉,
剩下的邊就是轉移概率,雖然不能∞,但矩陣快速冪跑個幾十萬次也不會差太多了。

但是我們還是要想想如何求得準確的概率。

首先我們知道經過root的概率是1,但其他點既可以轉移向root,也可以由root轉移,這就沒有辦法處理。

考慮到我們只需要得到單詞結尾節點的概率,而單詞結尾節點不會再向外轉移,
那麼經過單詞結尾節點的期望次數在數值上等於到達單詞結尾節點的概率。

那麼我們只需要求得經過每個點的期望次數即可。

假設AC自動機除去root=0s個節點,這樣就可以構造方程了:

對於root的處理:
最初,我們知道在不考慮其他轉移的情況下root必然經過一次,
ErootEiPi,root=1(i可以轉移向root)
那麼最初 矩陣 M0,0M0,s+1都為1,
如果有點i可以通過P的概率轉移向root,那麼M0,i=P

對於其他點的處理:
最初我們並不知道每個點經過次數的期望,
EiEjPj,i=0
移項,那麼

Mi,i=1Mi,j=Pj,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; }