LOJ#2004「SDOI2017」硬币游戏

Address

LOJ#2004 洛谷P3706 BZOJ4820


Solution

  • 尝试把这题讲得更为清楚些。

Part 1

  • 左转这道题的弱化版 BZOJ1444 [JSOI2009]有趣的游戏
  • 考虑建出 AC 自动机,则一个长度任意的字符串对应了 AC 自动机上的一条有向路径(可经过重复点)。
  • 直观的想法是设 P i P_i 表示到达 AC 自动机上结点 i i 的概率,最后答案即为那些被标记过的节点的 P P 值。
  • 但我们注意到,转移是会成环的,尽管我们能够用高斯消元解决成环的转移,但我们计算的概率是不能在 AC 自动机上走一圈再加回来的。
  • 归根结底,是我们设计的状态出现了问题。
  • 实际上应该设 E i E_i 表示经过 AC 自动机上结点 i i 的期望次数,就允许了一个结点被经过多次,并且到达被标记过的结点之后游戏就会结束,所以被标记过的结点的 E E 值在数值上就等于到达这个结点的概率。
  • G [ i ] [ j ] G[i][j] 表示 AC 自动机上的结点 i i 经过字符为 j j 的转移边到达的结点, 则对于每个未被标记过的结点 i i ,显然有转移: E G [ i ] [ j ] + = 1 2 E i E_{G[i][j]} += \frac{1}{2} E_i
  • 根据这些转移我们就能对于每个节点列出一个方程,注意我们的起点是根节点,所以关于根节点的方程的常数项要加一。
  • 用高斯消元解方程组即可, 因为 AC 自动机的结点数为 O ( n m ) O(nm) 级别,时间复杂度 O ( n 3 m 3 ) O(n^3m^3)

Part 2

  • 上一个做法的问题在于未知量的数量过多,实际上我们并不关心那些未被标记过的结点的 E E 值,而被标记过的结点只有 O ( n ) O(n) 级别。
  • 考虑设 E i E_i 表示经过 AC 自动机上第 i i 个被标记过的结点的期望次数,另外记 E 0 E_0 表示经过 AC 自动机上未被标记的结点的期望次数的和。
  • 现在我们需要根据这些变量的关系列出 n + 1 n + 1 个方程,最后同样用高斯消元解出答案。
  • 注意到 E i E_i 的实际意义是第 i i 个人获胜的概率。尽管我们也许能够构造出一个无穷长的序列使得没有人获胜,但这样必须不停地抛硬币,概率无限趋近于0。于是我们可以得到第一个方程: i = 1 n E i = 1 \sum \limits_{i = 1}^{n} E_i = 1
  • 考虑从 AC 自动机上一个未被标记的结点出发,按照第 i i 个人给定的硬币序列,依次经过 m m 条转移边,如果不考虑在这一过程中已经经过了被标记过的点而导致游戏结束,最后一定能走到第 i i 个被标记过的结点,即 E i = 1 2 m E 0 E_i = \frac{1}{2^m}E_0 (每走一步的概率都是 1 2 \frac{1}{2} )。
  • 现在要扣除那些不合法的情况(这也正是 E i E_i 可能互不相同的原因),考虑枚举这一过程中经过的第一个被标记过的点,假定它是第 j j 个被标记的点( j j 可以等于 i i ),那么一定存在一个 k ( 1 k < m ) k(1 \le k < m) 使得第 j j 个硬币序列长度为 k k 的后缀等于第 i i 个硬币序列长度为 k k 的前缀,据此可以对于每一个 i i 列出一个方程: E i + j = 1 n ( k = 1 m 1 [ p r e ( i , k ) = s u f ( j , k ) ] 1 2 m k ) E j 1 2 m E 0 = 0 E_i + \sum \limits_{j = 1}^{n} (\sum \limits_{k = 1}^{m - 1}[pre(i,k)=suf(j,k)] \frac{1}{2^{m - k}})E_j - \frac{1}{2^m}E_0 = 0
  • 其中 p r e ( i , k ) pre(i, k) 即表示第 i i 个硬币序列长度为 k k 的前缀, s u f ( j , k ) suf(j,k) 同理。
  • 实现时可以通过哈希暴力判断前后缀相等,时间复杂度 O ( n 3 ) O(n^3)

Code

#include <bits/stdc++.h>

const int mod1 = 1e9 + 7;
const int mod2 = 1e9 + 9;
const int N = 305;
char s[N][N]; double ex[N], f[N][N];
int a[N], p1[N], p2[N];
int pre1[N][N], pre2[N][N], suf1[N][N], suf2[N][N];
int n, m;

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; ++i)
		scanf("%s", s[i] + 1);
	ex[0] = 1;
	for (int i = 1; i <= m; ++i)
		ex[i] = 0.5 * ex[i - 1];

	p1[0] = p2[0] = 1;
	for (int i = 1; i <= m; ++i)
	{
		p1[i] = 29ll * p1[i - 1] % mod1;
		p2[i] = 31ll * p2[i - 1] % mod2;
	}
	for (int i = 1; i <= n; ++i)
	{
		for (int j = 1; j <= m; ++j)
			a[j] = s[i][j] == 'T' ? 5 : 7;
		for (int j = 1; j <= m; ++j)
		{
			pre1[i][j] = (29ll * pre1[i][j - 1] + a[j]) % mod1;
			pre2[i][j] = (31ll * pre2[i][j - 1] + a[j]) % mod2;
		}
		for (int j = 1; j <= m; ++j)
		{
			int tmp = m - j + 1;
			suf1[i][j] = (1ll * p1[j - 1] * a[tmp] + suf1[i][j - 1]) % mod1;
			suf2[i][j] = (1ll * p2[j - 1] * a[tmp] + suf2[i][j - 1]) % mod2;
		}
	}
	for (int i = 1; i <= n; ++i)
	{
		f[i][i] = 1.0;
		for (int j = 1; j <= n; ++j)
			for (int k = 1; k < m; ++k)
				if (suf1[j][k] == pre1[i][k] && suf2[j][k] == pre2[i][k])
					f[i][j] += ex[m - k];
		f[i][n + 1] -= ex[m];
	}
	for (int i = 1; i <= n; ++i)
		f[n + 1][i] = 1.0;
	f[n + 1][n + 2] = 1.0;

	++n;
	for (int i = 1; i <= n; ++i)
	{
		int p = i;
		for (int j = i + 1; j <= n; ++j)
			if (fabs(f[p][i]) < fabs(f[j][i]))
				p = j;
		if (p != i)
		{
			for (int j = i; j <= n + 1; ++j)
				std::swap(f[p][j], f[i][j]);
		}

		double tmp = f[i][i];
		for (int j = i; j <= n + 1; ++j)
			f[i][j] /= tmp;

		for (int j = 1; j <= n; ++j)
			if (j != i)
			{
				tmp = f[j][i];
				for (int k = i; k <= n + 1; ++k)
					f[j][k] -= tmp * f[i][k];
			}
	}
	for (int i = 1; i < n; ++i)
		printf("%.10lf\n", f[i][n + 1]);
}
发布了104 篇原创文章 · 获赞 125 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/bzjr_Log_x/article/details/100007360