BZOJ4820 SDOI2017硬币游戏(概率期望+高斯消元+kmp)
容易想到的做法是建出AC自动机,高斯消元。然而自动机上节点数量是nm的。
注意到我们要求的变量只有n个,考虑将其他不用求的节点合并为一个变量。这个变量即表示随机生成一个串,其不包含任何一个模板串的概率。
现在即有n+1个变量,考虑列出n+1个方程。设pi表示第i个人胜利的概率,显然有Σpi=1。然后对每个pi列一个方程,即考虑其胜利概率。在无胜利者的随机串后面接上这个串,这样这个人有可能成为胜利者,但也有可能之前的随机串加上这个串的一段前缀后已经包含了另一个串(可能是其自身),需要减掉这一部分。注意所有长度相同的串的出现概率都是均等的。设之前生成的随机串为S,当前考虑的串为si,加上si时在si之前出现的串为sj,那么这种情况出现的概率即为Σpj*0.5m-len,其中len为sj的后缀和si的前缀的任一匹配长度。因为S去掉某段后缀后得到的S'仍是不包含模板串的串,那么出现S'+sj的概率恰好为pj,而后面一部分则为恰好与si匹配上的概率。匹配长度显然可以用kmp算。
(感觉没一句话说清楚了
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 310 #define double long double char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,s[N][N],nxt[N][N]; double a[N][N]; void gauss() { for (int i=1;i<=n;i++) { int mx=i; for (int j=i+1;j<=n;j++) if (fabs(a[j][i])>fabs(a[mx][i])) mx=j; if (mx!=i) swap(a[i],a[mx]); for (int j=1;j<=n;j++) if (i!=j) { double t=a[j][i]/a[i][i]; for (int k=1;k<=n+1;k++) a[j][k]-=a[i][k]*t; } } } int main() { #ifndef ONLINE_JUDGE freopen("bzoj4820.in","r",stdin); freopen("bzoj4820.out","w",stdout); const char LL[]="%I64d\n"; #else const char LL[]="%lld\n"; #endif n=read(),m=read(); for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) s[i][j]=getc()=='T'; for (int i=1;i<=n;i++) { nxt[i][0]=-1; for (int j=1;j<=m;j++) { int k=nxt[i][j-1]; while (~k&&s[i][k+1]!=s[i][j]) k=nxt[i][k]; nxt[i][j]=k+1; } } for (int i=1;i<=n;i++) { a[i][i]=-1;a[i][n+1]=pow(0.5,m); for (int j=1;j<=n;j++) { int x=0; for (int k=1;k<=m;k++) { while (~x&&s[j][k]!=s[i][x+1]) x=nxt[i][x]; x++; } if (x==m) x=nxt[i][x]; while (x) a[i][j]-=pow(0.5,m-x),x=nxt[i][x]; } } for (int i=1;i<=n;i++) a[n+1][i]=1;a[n+1][n+2]=1; n++; gauss(); #undef double for (int i=1;i<n;i++) { double x=a[i][n+1]/a[i][i]; printf("%.10lf\n",x); } return 0; }