【BZOJ4820】硬币游戏(SDOI2017)-概率+高斯消元+KMP
测试地址:硬币游戏
做法:本题需要用到概率+高斯消元+KMP。
一看到这题,我们很快想到用JSOI2009-有趣的游戏那题一样,先建AC自动机,然后在AC自动机上建转移图,再高斯消元解方程……但是看到残酷的数据范围,我们就知道我们必须另辟蹊径了。
因为高斯消元的复杂度已经不能再降了,于是复杂度的瓶颈就在于方程太多了。因为我们要求的是,即以结尾且仅在字符串结尾匹配上串的概率,而这样的概率只有个,那么我们如果令为其他所有没匹配上的状态,如果我们能列出方程的话,方程的个数就是个,可以接受。
考虑在一个没匹配到任何串的串后面接一个串,那么一旦接完这个游戏就会立刻停止,但是不一定在接完后游戏才停止,有可能在接完之前先匹配上一个串。也就是说,的一个后缀和的一个前缀组成了一个。那么这个字符串就可以表示成,的一个前缀的一个后缀。因为的一个前缀这一部分出现的概率就等于,而在后面接出一个特定长度的串的概率是,所以我们令剩下的后缀长度为,这种情况出现的概率就是。
那么,对于每个串,对于每种它的一个后缀和的一个前缀的匹配,设匹配长度为,发生的概率都是,而对于所有的这些情况,发生的概率总和显然是(即出现这个串的概率),那么我们就可以列出一个方程了。对于每个,我们都可以列出一个这样的方程,但是现在未知数有个(因为有),方程只有个,无法求解。这时候我们发现有一个隐含条件:,把这个当做方程,就可以高斯消元解方程组了,时间复杂度为。
现在我们的问题就是求出对方程的贡献,我们其实只要将作为模式串做KMP,那么最后匹配到的那个前缀以及能通过指针走到的所有前缀,都是的一个后缀,累加贡献即可,时间复杂度为。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,nxt[310];
char s[310][310];
long double tot[310],g[310][310]={0},pwr[310];
void build(int i)
{
nxt[0]=-1;tot[0]=pwr[m-1];
int last;
for(int j=1;j<m;j++)
{
last=nxt[j-1];
while(last!=-1&&s[i][last+1]!=s[i][j]) last=nxt[last];
if (s[i][last+1]==s[i][j]) nxt[j]=last+1;
else nxt[j]=last;
if (s[i][last+1]==s[i][j]) tot[j]=tot[nxt[j]]+pwr[m-j-1];
else tot[j]=pwr[m-j-1];
}
}
void calc(int i,int j)
{
int last=-1;
for(int k=0;k<m;k++)
{
while(last!=-1&&s[i][last+1]!=s[j][k]) last=nxt[last];
if (s[i][last+1]==s[j][k]) last++;
}
if (last!=-1) g[i][j]=tot[last];
}
void gauss(int n)
{
for(int i=1;i<=n;i++)
{
int mx=i;
for(int j=i+1;j<=n;j++)
if (fabs(g[j][i])>fabs(g[mx][i])) mx=j;
for(int j=i;j<=n+1;j++)
swap(g[i][j],g[mx][j]);
for(int j=i+1;j<=n;j++)
{
for(int k=i+1;k<=n+1;k++)
g[j][k]-=g[j][i]*g[i][k]/g[i][i];
g[j][i]=0.0;
}
}
for(int i=n;i>=1;i--)
for(int j=1;j<i;j++)
{
g[j][n+1]-=g[j][i]*g[i][n+1]/g[i][i];
g[j][i]=0.0;
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",s[i]);
pwr[0]=1.0;
for(int i=1;i<=m;i++)
pwr[i]=pwr[i-1]*0.5;
for(int i=1;i<=n;i++)
{
build(i);
for(int j=1;j<=n;j++)
calc(i,j);
}
for(int i=1;i<=n;i++)
g[i][n+1]=-pwr[m];
for(int i=1;i<=n;i++)
g[n+1][i]=1.0;
g[n+1][n+2]=1.0;
gauss(n+1);
for(int i=1;i<=n;i++)
printf("%.10Lf\n",g[i][n+2]/g[i][i]);
return 0;
}