1. 程式人生 > >BZOJ 4820 [SDOI2017] 硬幣遊戲

BZOJ 4820 [SDOI2017] 硬幣遊戲

ring 們的 整數 vector 序列 數據 wap 減少 get()

Description

  周末同學們非常無聊,有人提議,咱們扔硬幣玩吧,誰扔的硬幣正面次數多誰勝利。大家紛紛覺得這個遊戲非常符合同學們的特色,但只是扔硬幣實在是太單調了。同學們覺得要加強趣味性,所以要找一個同學扔很多很多次硬幣,其他同學記錄下正反面情況。用H表示正面朝上,用T表示反面朝上,扔很多次硬幣後,會得到一個硬幣序列。比如HTT表示第一次正面朝上,後兩次反面朝上。但扔到什麽時候停止呢?大家提議,選出\(n\)個同學,每個同學猜一個長度為\(m\)的序列,當某一個同學猜的序列在硬幣序列中出現時,就不再扔硬幣了,並且這個同學勝利,為了保證只有一個同學勝利,同學們猜的\(n\)個序列兩兩不同。很快,\(n\)

個同學猜好序列,然後進入了緊張而又刺激的扔硬幣環節。你想知道,如果硬幣正反面朝上的概率相同,每個同學勝利的概率是多少。

Input

  第一行兩個整數\(n,m\)
  接下裏n行,每行一個長度為m的字符串,表示第i個同學猜的序列。
  \(1<=n,m<=300\)

Output

  輸出n行,第i行表示第i個同學勝利的概率。
  輸出與標準輸出的絕對誤差不超過\(1e-6\)即視為正確。

Sample Input

3 3
THT
TTH
HTT

Sample Output

0.3333333333
0.2500000000
0.4166666667

數據規模

\(1\leq n,m \leq 300\)

乍一看是 [JSOI2009]有趣的遊戲,但是數據範圍不支持。於是標解就用了個十分神仙的方法減少了方程數。

我們還是從 [JSOI2009]有趣的遊戲 的思路開始分析。我們發現中間狀態太多了,所以我們將轉移到中間狀態的期望設為\(x_0\)。然後\(x_i (1\leq i \leq n)\)表示第\(i\)個人勝利的期望。

因為該題依然期望\(=\)概率,所以依然有\(x_1+x_2+...+x_n=1\)

然後就是最神仙的方程了。

我們設\(P(i)\)表示在遊戲中途(未結束時)出現的所有字符串後面接上第\(i\)個字符串得到的字符串出現的期望。

首先,$P(i)=\frac{1}{2^m}x_0 \(。我們在任意一個中間狀態後面加上第\)i\(個字符串,就可以得到想要的結果。因為每種字符出現概率相同,所以出現第\)

i\(個串的概率為\)\frac{1}{2^m}\(。我們再考慮用用其他的變量表示\)P(i)$。

顯然出現了這種字符串代表遊戲一定結束了,但是遊戲不一定在這個時候結束,贏家不一定是\(i\),因為可能在插入第\(i\)個串的中途就匹配上了一個字符串。我們發現出現這種情況一定是一個字符串\(j\)的長度為\(k(1\leq k < m)\)的後綴與\(i\)字符串的長度為\(k\)的前綴相同(註意這裏\(j\)是可以等於\(i\)的)。畫個圖就很好理解了。

然後再在後面補上\(m-k\)個字符就可以了。這部分的概率是\(\frac{1}{2^{m-k}}\)

我們考慮出現上述情況的時候贏家一定是\(j\),所以第\(i\)個字符串對\(P(i)\)的貢獻就是\(g(i,j)\displaystyle \sum_{k=1}^{m-1}[j的k後綴=i的k前綴]\frac{1}{2^{m-k}}\)

快速求出所有\(k\)可以考慮用\(kmp\)

於是我們有列出了\(n\)個方程:$\displaystyle \sum_{j=1}^ng(i,j)x_j+x_i=\frac{1}{2^m}x_0 $。

然後解方程就行了。

代碼:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<set>
#include<queue>
#include<vector>
#include<map>
#include<iomanip>
#define ll long long
#define ld long double
#define N 305

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n,m;
char s[N][N];
ld a[N][N],pw[N];
int nxt[N];
void Get_nxt(char *s) {
    memset(nxt,0,sizeof(nxt));
    nxt[0]=-1;
    for(int i=1;i<=m;i++) {
        int j=nxt[i-1];
        while(j!=-1&&s[j+1]!=s[i]) j=nxt[j];
        nxt[i]=j+1; 
    }
}
ld cal(char *s,char *t) {
    int now=0;
    ld ans=0;
    for(int i=1;i<=m;i++) {
        while(now!=-1&&s[now+1]!=t[i]) now=nxt[now];
        now++;
    }
    if(now==m) now=nxt[now];
    while(now) {
        ans+=pw[m-now];
        now=nxt[now];
    }
    return ans;
}
ld ans[N];
void Guass() {
    for(int i=0;i<=n;i++) {
        for(int j=i;j<=n;j++)
            if(fabs(a[i][i])<fabs(a[j][i])) swap(a[i],a[j]);
        for(int j=i+1;j<=n;j++) {
            ld tem=a[j][i]/a[i][i];
            for(int k=i;k<=n+1;k++) a[j][k]-=tem*a[i][k];
        }
    }
    for(int i=n;i>=1;i--) {
        for(int j=n;j>i;j--) {
            a[i][n+1]-=ans[j]*a[i][j];
        }
        ans[i]=a[i][n+1]/a[i][i];
    }
}

int main() {
    n=Get(),m=Get();
    pw[0]=1;
    for(int i=1;i<=m;i++) pw[i]=pw[i-1]*0.5;
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++) a[0][i]=1;
    a[0][n+1]=1;
    for(int i=1;i<=n;i++) {
        Get_nxt(s[i]);
        for(int j=1;j<=n;j++) {
            a[i][j]=cal(s[i],s[j]);
        }
        a[i][0]=-pw[m];
        a[i][i]++;
    }
    Guass();
    for(int i=1;i<=n;i++) cout<<fixed<<setprecision(10)<<ans[i]<<"\n";
    return 0;
}

BZOJ 4820 [SDOI2017] 硬幣遊戲