[BZOJ4820][SDOI2017]硬币游戏(高斯消元+KMP)

时间:2024-12-03 22:36:02

比较神的一道题,正解比较难以理解。

首先不难得出一个(nm)^3的算法,对所有串建AC自动机,将在每个点停止的概率作为未知数做高斯消元即可。

可以证明,AC自动机上所有不是模式串终止节点的点可以看成一个点,不妨合并为同一个点(n+1号点)。

对于一个模式串,它肯定是从n+1号点走了m步后到达的,概率为0.5^m。但是有可能还没走到m步的时候就已经匹配上另一个串了(可能是它本身),那么这另一个串的后缀一定是这个串的前缀。枚举这种串的位置,将它的概率贡献减去。

这样就是单模式串匹配问题了,可以用KMP解决。现在已经有n个方程了,再加上p[1]+p[2]+...+p[n+1]=1一共n+1个方程,高斯消元解出p[1],...,p[n+1]这n+1个未知数即可。

 #include<cmath>
#include<cstdio>
#include<algorithm>
#define rep(i,l,r) for (int i=(l); i<=(r); i++)
using namespace std; const int N=;
int n,m,s[N][N],nxt[N][N];
double a[N][N]; void Gauss(int n){
rep(i,,n){
int k=i;
rep(j,i+,n) if (fabs(a[j][i])>fabs(a[k][i])) k=j;
swap(a[i],a[k]);
rep(j,,n) if (i!=j){
double t=a[j][i]/a[i][i];
rep(k,,n+) a[j][k]-=t*a[i][k];
}
}
} int main(){
freopen("bzoj4820.in","r",stdin);
freopen("bzoj4820.out","w",stdout);
scanf("%d%d",&n,&m); char ch;
rep(i,,n) rep(j,,m) scanf(" %c",&ch),s[i][j]=(ch=='T');
rep(i,,n){
nxt[i][]=nxt[i][]=; int p=;
rep(j,,m){
while (p && s[i][p+]!=s[i][j]) p=nxt[i][p];
if (s[i][p+]==s[i][j]) nxt[i][j]=++p; else nxt[i][j]=;
}
}
rep(i,,n){
a[i][i]=-; a[i][n+]=pow(0.5,m);
rep(j,,n){
int p=;
rep(k,,m){
while (p && s[i][p+]!=s[j][k]) p=nxt[i][p];
if (s[i][p+]==s[j][k]) p++;
}
if (p==m) p=nxt[i][p];
while (p) a[i][j]-=pow(0.5,m-p),p=nxt[i][p];
}
}
rep(i,,n) a[n+][i]=; a[n+][n+]=; Gauss(n+);
rep(i,,n) printf("%.10lf\n",a[i][n+]/a[i][i]);
return ;
}