[BZOJ4820][SDOI2017]硬币游戏(高斯消元+KMP)
比较神的一道题,正解比较难以理解。
首先不难得出一个(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个未知数即可。
1 #include<cmath> 2 #include<cstdio> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 5 using namespace std; 6 7 const int N=310; 8 int n,m,s[N][N],nxt[N][N]; 9 double a[N][N]; 10 11 void Gauss(int n){ 12 rep(i,1,n){ 13 int k=i; 14 rep(j,i+1,n) if (fabs(a[j][i])>fabs(a[k][i])) k=j; 15 swap(a[i],a[k]); 16 rep(j,1,n) if (i!=j){ 17 double t=a[j][i]/a[i][i]; 18 rep(k,1,n+1) a[j][k]-=t*a[i][k]; 19 } 20 } 21 } 22 23 int main(){ 24 freopen("bzoj4820.in","r",stdin); 25 freopen("bzoj4820.out","w",stdout); 26 scanf("%d%d",&n,&m); char ch; 27 rep(i,1,n) rep(j,1,m) scanf(" %c",&ch),s[i][j]=(ch=='T'); 28 rep(i,1,n){ 29 nxt[i][0]=nxt[i][1]=0; int p=0; 30 rep(j,2,m){ 31 while (p && s[i][p+1]!=s[i][j]) p=nxt[i][p]; 32 if (s[i][p+1]==s[i][j]) nxt[i][j]=++p; else nxt[i][j]=0; 33 } 34 } 35 rep(i,1,n){ 36 a[i][i]=-1; a[i][n+1]=pow(0.5,m); 37 rep(j,1,n){ 38 int p=0; 39 rep(k,1,m){ 40 while (p && s[i][p+1]!=s[j][k]) p=nxt[i][p]; 41 if (s[i][p+1]==s[j][k]) p++; 42 } 43 if (p==m) p=nxt[i][p]; 44 while (p) a[i][j]-=pow(0.5,m-p),p=nxt[i][p]; 45 } 46 } 47 rep(i,1,n) a[n+1][i]=1; a[n+1][n+2]=1; Gauss(n+1); 48 rep(i,1,n) printf("%.10lf\n",a[i][n+2]/a[i][i]); 49 return 0; 50 }