SDOI2017 硬幣遊戲
題意
有\(n\)個人,第\(k\)個人猜一個長度為\(m\)的01串\(A_k\),互不相同;接下來依次生成一列隨機變數\(\{X_i\}\),其中每一個等概率為0或1。當\(A_k\)在這一列隨機變數中連續出現時\(k\)勝利,並且其他人不再勝利。問每個人勝利的概率\(\{P_k\}\)。(\(1\le n,m\le 300\))
題解
有一個樸素做法。建出AC自動機,在每一個時刻,隨著隨機變數的產生,狀態會進行一系列轉移,停留在一個特定節點。所以在自動機上依據轉移邊構造一系列方程,其中每個變量表示某個特定節點被經過次數的期望。
注意這一步轉化很重要,因為在狀態轉移過程中會不止一次經過一個節點。設想,如果在大多情況會多次經過一個節點\(a\)
那麼怎樣體現勝利概率呢?很簡單,只要不允許終止狀態產生任何後續轉移,那麼每種情況只會有唯一的一個終止狀態被經過唯一一次,符合題意,而終止節點被經過次數的期望就是勝利概率。高斯消元即可。這道題是《JSOI2009 有趣的遊戲》。
然而這題資料範圍太大了,高斯消元\(\Omicron(n^3m^3)\)
如果做過《CTSC2006 歌唱王國》(我也寫過題解)那麼對於它的生成函式做法一定記憶猶新(雖然洛谷第一篇用離散時間鞅的做法也很巧妙)。這裡也考慮這種思路。考慮當前正停留在任意一個沒有結束的狀態\(Y\),接下來發生了一個特殊事件\(V_k\)
第二種則是考慮,根據\(V_k\)的定義,設有人勝利的時間是隨機變數\(T\),則\(V_k\)發生的條件下,有\(T\in [|Y|+1, |Y|+m]\),勝利者一定已經產生(不一定是\(k\)),所以考慮列舉\(T\)的取值為\(|Y|+t\)和獲勝的人\(p\),然後讓勝利以後繼續生成出剩下的狀態,於是得到:
\[E(Tot(V_k)) = \sum\limits_{p=1}^{n}\sum\limits_{t=1}^{m}[Pre_t(A_k) = Suf_t(A_p)]2^{-(m-t)}f(p) \]結合第一個式子,我們就能得到一個\(n\)個方程。其中唯一不知道的量就是\(\left(\sum\limits_{Y}E\left(Y\right)\right)\),直接設它為一個未知量,再結合遊戲必定終止,得到\(\sum\limits_{k=1}^n f_k = 1\),總共\(n+1\)個方程和未知量,高斯消元即可。
程式碼
// Author: kyEEcccccc
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
int t0 = clock();
const int N = 305, PP = 131;
int n, m;
ULL hsh[N][N], rhsh[N][N], kp[N];
double pw2[N], mat[N][N];
int main(void)
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
ios::sync_with_stdio(0), cin.tie(nullptr);
pw2[0] = 1; kp[0] = 1;
for (int i = 1; i <= 300; ++i)
pw2[i] = pw2[i-1] / 2, kp[i] = kp[i-1] * PP;
cin >> n >> m;
for (int i = 0; i < n; ++i)
{
string s;
cin >> s;
for (int j = 1; j <= m; ++j)
hsh[i][j] = hsh[i][j-1] * PP + (s[j-1] == 'H' ? 3 : 5);
for (int j = m; j >= 1; --j)
rhsh[i][j] = rhsh[i][j+1] + kp[m-j] * (s[j-1] == 'H' ? 3 : 5);
}
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n+1; ++j)
mat[i][j] = 0;
for (int i = 0; i < n; ++i)
mat[n][i] = 1;
mat[n][n+1] = 1;
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < n; ++j)
for (int k = 1; k <= m; ++k)
if (hsh[i][k] == rhsh[j][m-k+1])
mat[i][j] += pw2[m-k];
mat[i][n] += -pw2[m];
}
// for (int i = 0; i < n; ++i)
// for (int j = 0; j <= n+1; ++j)
// cout << mat[i][j] << " \n"[j==n+1];
for (int i = 0; i <= n; ++i)
{
double t = mat[i][i];
for (int j = 0; j <= n+1; ++j)
mat[i][j] /= t;
for (int j = 0; j <= n; ++j)
{
if (j == i) continue;
double d = mat[j][i];
for (int k = 0; k <= n+1; ++k)
mat[j][k] -= mat[i][k] * d;
}
}
// for (int i = 0; i <= n; ++i)
// for (int j = 0; j <= n+1; ++j)
// cout << mat[i][j] << " \n"[j==n+1];
for (int i = 0; i < n; ++i)
cout << fixed << setprecision(10) << mat[i][n+1] << '\n';
// cerr << double(clock() - t0) / CLOCKS_PER_SEC << '\n';
return 0;
}