1. 程式人生 > >BZOJ4820 SDOI2017硬幣遊戲(概率期望+高斯消元+kmp)

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; }