正题
题目链接:https://www.luogu.com.cn/problem/P3706
题目大意
给出 nnn 个长度为 mmm 的 H/TH/TH/T 串。
开始一个空序列,每次随机在后面加一个 H/TH/TH/T ,求每个串第一次出现的概率。
1≤n,m≤3001\leq n,m\leq 3001≤n,m≤300
解题思路
数据范围显然不能在AC自动机上高斯消元,所以得考虑别的方法。
考虑一个很妙的做法,设一个状态NNN表示目前还没有匹配完成的串,然后考虑串A=HHTA=HHTA=HHT和串B=THHB=THHB=THH,那么在NNN后面直接插入一个AAA的概率就是p(N+A)=p(N)×2−mp(N+A)=p(N)\times 2^{-m}p(N+A)=p(N)×2−m
但是考虑到有可能NNN先拼出了BBB然后再拼出AAA,此时考虑BBB的后缀对应AAA前缀的有H,HHH,HHH,HH。那么就有
p(N)×2−m=p(N+A)=p(A)+p(B)×2−2+p(B)×2−1p(N)\times 2^{-m}=p(N+A)=p(A)+p(B)\times 2^{-2}+p(B)\times 2^{-1}p(N)×2−m=p(N+A)=p(A)+p(B)×2−2+p(B)×2−1
这样不难发现对于别的串如果它的一些后缀是这个串的前缀那么就会产生一些概率,用字符串hashhashhash匹配即可。
然后会发现还是少了一个方程,最后一个就是所有串的概率和为111就好了。
这样就有n+1n+1n+1个方程了。
时间复杂度:O(n2m+n3)O(n^2m+n^3)O(n2m+n3)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ull unsigned long long
using namespace std;
const int N=310;
const ull g=131;
int n,m;
double a[N][N],b[N],pw[N];
ull h[N][N],p[N];char s[N];
ull geth(int x,int l,int r)
{return h[x][r]-h[x][l-1]*p[r-l+1];}
void Gauss(int n){for(int i=1;i<=n;i++){int z=i;for(int j=i+1;j<=n;j++)if(a[j][i]>a[z][i])z=i;swap(a[i],a[z]);double x=a[i][i];b[i]/=x;for(int j=i;j<=n;j++)a[i][j]/=x;for(int j=i+1;j<=n;j++){double rate=-a[j][i];for(int k=i;k<=n;k++)a[j][k]+=rate*a[i][k];b[j]+=rate*b[i];}}for(int i=n;i>=1;i--){for(int j=1;j<i;j++){b[j]-=a[j][i]*b[i];a[j][i]=0;}}return;
}
int main()
{scanf("%d%d",&n,&m);p[0]=1;pw[0]=1;for(int i=1;i<=m;i++)pw[i]=pw[i-1]*0.5,p[i]=p[i-1]*g;for(int i=1;i<=n;i++){scanf("%s",s+1);for(int j=1;j<=m;j++)h[i][j]=h[i][j-1]*g+s[j];}for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)for(int k=1;k<=m;k++)if(geth(i,1,k)==geth(j,m-k+1,m))a[i][j]+=pw[m-k];for(int i=1;i<=n;i++)a[i][n+1]=-pw[m],a[n+1][i]=1;b[n+1]=1;Gauss(n+1);for(int i=1;i<=n;i++)printf("%.12lf\n",b[i]);return 0;
}