SDOI2017 硬币游戏

题意

\(n\) 个人,第 \(k\) 个人猜一个长度为 \(m\) 的01串 \(A_k\),互不相同;接下来依次生成一列随机变量 \(\{X_i\}\),其中每一个等概率为0或1。当 \(A_k\) 在这一列随机变量中连续出现时 \(k\) 胜利,并且其他人不再胜利。问每个人胜利的概率 \(\{P_k\}\)。(\(1\le n,m\le 300\))

题解

有一个朴素做法。建出AC自动机,在每一个时刻,随着随机变量的产生,状态会进行一系列转移,停留在一个特定节点。所以在自动机上依据转移边构造一系列方程,其中每个变量表示某个特定节点被经过次数的期望。

注意这一步转化很重要,因为在状态转移过程中会不止一次经过一个节点。设想,如果在大多情况会多次经过一个节点 \(a\),但是往往只经过一次节点 \(b\),然而两者“被经过”的概率相同,那么假设终止状态 \(x\)\(a\) 转移,\(y\)\(b\) 转移,则转移到 \(x\) 的概率显然比 \(y\) 更高。然而,如果只记录转移过程中“是否经过”某个节点,就不能体现多次经过的贡献。而经过次数的数学期望,就体现了转移次数这个重要信息。

那么怎样体现胜利概率呢?很简单,只要不允许终止状态产生任何后续转移,那么每种情况只会有唯一的一个终止状态被经过唯一一次,符合题意,而终止节点被经过次数的期望就是胜利概率。高斯消元即可。这道题是《JSOI2009 有趣的游戏》。

然而这题数据范围太大了,高斯消元 \(O(n^3m^3)\) 不能接受。当遇到这种情况时,往往要考虑压缩状态,忽略冗余信息的方法。这道题需要的仅仅是每个人的终止节点的次数期望,这些变量必须保留,而前面那些状态只要转移到最终状态就行了,至于如何转移,从哪里转移,互相怎么转移,这些信息应该试图去简化、合并。设到达一个状态 \(S\) 的次数为随机变量 \(Tot(S)\),第 \(k\) 个人赢的概率(也就是它的状态到达次数)为 \(f(k)\)

如果做过《CTSC2006 歌唱王国》(我也写过题解)那么对于它的生成函数做法一定记忆犹新(虽然洛谷第一篇用离散时间鞅的做法也很巧妙)。这里也考虑这种思路。考虑当前正停留在任意一个没有结束的状态 \(Y\),接下来发生了一个特殊事件 \(V_k\),也就是接下来随机出的长度为 \(m\) 的序列恰好就是 \(A_k\)注意这时可能已经终止,仍然无条件继续生成序列,但是正如上面所说,胜利以后状态不转移,也不继续累计到达次数。 正如《歌唱王国》的处理思路,事件 \(V_k\) 发生次数的期望有两种计算方法,第一种(这是显然的):

\[E(Tot(V_k)) = \left(\sum\limits_{Y}E\left(Y\right)\right)\cdot 2^{-m} \]

第二种则是考虑,根据 \(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;
}
posted @ 2022-12-05 11:50  kyEEcccccc  阅读(62)  评论(0编辑  收藏  举报