[SDOI 2017] 硬币游戏

题目链接:LOJluogu

Task 1 \(1\le n,m\le 3\)

枚举最后几位的情况,或者枚举三个字符串当前已经匹配了多少位,可以得出状态转移的形式,高斯消元即可。

Task 2 \(1\le n,m\le 18\)

做法与 [JSOI2009]有趣的游戏 相同,时间复杂度 \(O(n^3m^3)\),具体的因为本弱不会 AC 自动机所以略过。

Task 3 \(n=2\)

这一部分较为重要

设两个人的串分别是 \(A,B\),假设当前游戏还没结束,且字符串为 \(S\),我们知道如果直接在 \(S\) 后面加上一个 \(A\) 游戏一定能结束,但是可能会出现在中途结束游戏的情况。

例:A=101,B=110

  • 如果当前 \(S\)10 结尾,那么加入第一个 1 的时候就直接结束,第一个人获胜,多加了 01
  • 如果当前 \(S\)1 结尾,那么加入 10 时结束,第二个人获胜,多加了 1
  • 其余情况,第一个人获胜。

于是可以得到一个等式: \(S101=A01+B1+A\),就有 \(S\cdot \frac{1}{8}=A\cdot \frac{1}{4}+B\cdot \frac{1}{2}+A\)

这个式子的来源是,每个形如 \(S101\) 的字符串都可以不重不漏地分解成三种不同的类型,于是这些字符串出现的概率必然相同。

我们再来看看直接在 \(S\) 后面加上一个 \(B\) 的情况,同样能够得到 \(S110=A10+B\),于是就有 \(S\cdot \frac{1}{8}=A\cdot \frac{1}{4}+B\)\(\frac{1}{8}\)\(\frac{1}{4}\) 分别为在一个状态后面添加 \(3\) 个或 \(2\) 个给定字母的概率。

通过上述两个式子我们是能够知晓 \(A,B\) 之间的关系的,但是并不方便我们直接得出结果。

我们还知道,一定有 \(P_A+P_B=1\) ,所以能够得到一个三元一次方程组 $$\begin{cases} A+B=1\ S=10A+4B\ S=2A+8B\ \end{cases} $$

解得

\[\begin{cases} S=6\\ A=\frac{1}{3}\\ B=\frac{2}{3}\\ \end{cases} \]

于是在这种情况下,我们就得出了两人获胜的概率。

总结一下不难得出,当我们考虑加 \(A\) 串的时候,能够对式子产生贡献的情况是 \(A\) 串的一个前缀和 \(x\) 串(可能是 \(A\)\(B\))的一个后缀发生了重合,假设重合部分的长度为 \(L\) ,那么式子右边就会多上 \(x\cdot \frac{1}{2^{m-L}}\) ,因为剩下还有 \(m-L\) 长度的串可以加进去。

于是我们就可以得出一个标准化的式子:

\[S\cdot \frac{1}{2^m}=\sum_{pre(A,L)=suf(A,L)} A\cdot \frac{1}{2^{m-L}}+\sum_{pre(A,L)=suf(B,L)} B\cdot \frac{1}{2^{m-L}} \]

对加 \(B\) 串的情况,也能得到类似的结果,于是我们通过枚举 \(L\) 判断对应前后缀是否相同,并手工计算这两个方程里 \(A\)\(B\) 的系数,就能算出 \(A,B\) 之间的比值,从而求解。

Task 4 \(1\le n,m\le 300\)

实际上Task 3的做法离正解已经无限接近了。

\(pre(i,L)\) 表示第 \(i\) 个字符串长度为 \(L\) 的前缀,\(suf(i,L)\) 同理,那么根据Task 3的结论,可以得出方程组的第 \(i\) 个式子为:

\[S\cdot \frac{1}{2^m}=\sum_{j=1}^{n}\sum_{pre(i,L)=suf(j,L)} P_j\cdot \frac{1}{2^{m-L}} \]

再结合式子 \(\sum_{i=1}^{n}P_i=1\),即可联立方程组高斯消元求解。

接下去问题就在于如何快速判定 \(pre(i,L)=suf(j,L)\),用各类字符串科技或者直接 \(\texttt{hash}\) 即可,本人用的是 \(\texttt{hash}\)

#include<bits/stdc++.h>
using namespace std;
#define N 310
#define ld double
const long long P=37;
const int MOD=1000000007;
int n,m,pre[N][N],suf[N][N];
ld a[N][N],p[N],T;
char s[N];
void Gauss(int n)
{
	for(int i=1;i<=n;i++){
		int t=0;
		for(int j=i;j<=n;j++)
			if(a[j][i]!=0){
				t=j;
				break;
			}
		swap(a[i],a[t]);
		T=1.0/a[i][i];
		for(int j=i+1;j<=n+1;j++)
			a[i][j]*=T;
		a[i][i]=1; 
		for(int j=1;j<=n;j++)if(i!=j){
			T=a[j][i],a[j][i]=0;
			for(int k=i+1;k<=n+1;++k)
				a[j][k]-=T*a[i][k];
		}
	}
	for(int i=1;i<n;i++)printf("%.6f\n",a[i][n+1]);
}
int main()
{
	for(int i=0;i<N;i++)
		p[i]=pow(0.5,i);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%s",s+1);
		for(int j=1;j<=m;j++)
			pre[i][j]=(P*pre[i][j-1]+s[j]-'A')%MOD;
		for(long long j=1,k=1;j<=m;j++,k=P*k%MOD)
			suf[i][j]=(k*(s[m-j+1]-'A')+suf[i][j-1])%MOD;
		a[i][n+1]=-p[m];
		a[n+1][i]=1;
	}
	a[n+1][n+2]=1;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	for(int L=1;L<=m;L++)
	if(pre[i][L]==suf[j][L])
		a[i][j]+=p[m-L];
	Gauss(n+1);
}

其实可以发现,关于 \(S\) 的系数是可以任取的,因为在方程中 \(S\) 相当于只是一个辅助变量,所以代码中的 a[i][n+1]=-p[m]; 是可以把系数改成任意数字的(如 \(114514\))。

posted @ 2022-07-18 21:02  DeaphetS  阅读(112)  评论(0编辑  收藏  举报