1. 程式人生 > 其它 >題解 「SDOI2017」硬幣遊戲

題解 「SDOI2017」硬幣遊戲

題目傳送門

Description

週末同學們非常無聊,有人提議,咱們扔硬幣玩吧,誰扔的硬幣正面次數多誰勝利。

大家紛紛覺得這個遊戲非常符合同學們的特色,但只是扔硬幣實在是太單調了。

同學們覺得要加強趣味性,所以要找一個同學扔很多很多次硬幣,其他同學記錄下正反面情況。

用 $ \texttt{H} $ 表示正面朝上, 用 $ \texttt{T} $ 表示反面朝上,扔很多次硬幣後,會得到一個硬幣序列。比如 $ \texttt{HTT} $ 表示第一次正面朝上,後兩次反面朝上。

但扔到什麼時候停止呢?大家提議,選出 $ n $ 個同學, 每個同學猜一個長度為 $ m $ 的序列,當某一個同學猜的序列在硬幣序列中出現時,就不再扔硬幣了,並且這個同學勝利。為了保證只有一個同學勝利,同學們猜的 $ n $ 個序列兩兩不同。

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

對於 $ 10% $ 的資料,$ 1 \leq n, m \leq 3 $;
對於 $ 40% $ 的資料,$ 1 \leq n, m \leq 18 $;
對於另外 $ 20%$ 的資料,$ n = 2 $;
對於 $ 100% $ 的資料,$ 1 \leq n, m \leq 300 $。

Solution

考慮使用概率生成函式。

我們設 \([x^n]G(x)\) 表示在 \(n\) 次操作後仍未結束的概率,\([x^n]F_i(x)\) 表示在 \(n\) 次操作後第 \(i\)

個人獲勝的概率,\(a_{i,j,k}\) 表示第 \(i\) 個人的 \([1,k]\) 與第 \(j\) 個人的 \([m-k+1,m]\) 是否相同。

那我們可以得到:

\[G(x)+\sum_{i=1}^{n} F_i(x)=1+x·G(x) \]\[G(x)(\frac{1}{2})^mx^m=\sum_{j=1}^{n}\sum_{k=1}^{m}a_{i,j,k}(\frac{1}{2})^{m-k}x^{m-k}F_j(x) \]

因為你最後只需要求出 \(F_i(1)\) ,所以可以把 \(x\) 都當成 \(1\) ,然後第一個式子就變為了:

\[\sum_{i=1}^{n} F_i(1)=1 \]

(似乎很顯然的不需要第一個式子)

然後你就有 \(n+1\) 個方程,可以直接高斯消元了。

Code

#include <bits/stdc++.h>

using namespace std;



#define Int register int

#define MAXN 305



template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}

template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);}

template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

template <typename T> inline void chkmax (T &a,T b){a = max (a,b);}

template <typename T> inline void chkmin (T &a,T b){a = min (a,b);}



int n,m;

char S[MAXN][MAXN];

bool a[MAXN][MAXN][MAXN];

double mat[MAXN][MAXN],pw2[MAXN],ans[MAXN];



#define seed 1000000007

#define ull unsigned long long

ull pw[MAXN],pre[MAXN][MAXN],suf[MAXN][MAXN];



void gauss (){

	int up = n + 1;

	for (Int i = 1;i <= up;++ i){

		int ind = i;

		for (Int j = i;j <= up;++ j) if (abs (mat[j][i]) >= 1e-7){ind = j;break;}

		if (ind ^ i) swap (mat[i],mat[ind]);

		for (Int j = i + 1;j <= up;++ j){

			double det = mat[j][i] / mat[i][i];

			for (Int k = i;k <= up + 1;++ k) mat[j][k] -= mat[i][k] * det;

		}

	}

	for (Int i = up;i >= 0;-- i){

		for (Int j = i + 1;j <= up;++ j) mat[i][up + 1] -= mat[i][j] * ans[j];

		ans[i] = mat[i][up + 1] / mat[i][i];

	}

}



signed main (){

	read (n,m),pw[0] = pw2[0] = 1;

	for (Int i = 1;i <= m;++ i) pw[i] = pw[i - 1] * seed,pw2[i] = pw2[i - 1] * 2;

	for (Int i = 1;i <= n;++ i) scanf ("%s",S[i] + 1);

	for (Int i = 1;i <= n;++ i){

		for (Int j = 1;j <= m;++ j) pre[i][j] = pre[i][j - 1] * seed + (S[i][j] == 'T');

		for (Int j = m;j >= 1;-- j) suf[i][j] = suf[i][j + 1] + (S[i][j] == 'T') * pw[m - j];

	}

	for (Int i = 1;i <= n;++ i) 

		for (Int j = 1;j <= n;++ j)

			for (Int k = 1;k <= m;++ k)

				a[i][j][k] = (pre[i][k] == suf[j][m - k + 1]);

	for (Int i = 1;i <= n;++ i){

		for (Int j = 1;j <= n;++ j)

			for (Int k = 1;k <= m;++ k)

				if (a[i][j][k]) mat[i][j] += pw2[k];

		mat[i][n + 1] = -1;

	}

	for (Int i = 1;i <= n;++ i) mat[n + 1][i] = 1;mat[n + 1][n + 2] = 1;

	gauss ();for (Int i = 1;i <= n;++ i) printf ("%.6f\n",ans[i]);

	return 0;

}