1444: [Jsoi2009]有趣的游戏
4820: [Sdoi2017]硬币游戏
这两道题都是关于不断随机生成字符后求出现给定字符串的概率的问题。
第一题数据范围较小,将串建成AC自动机以后,以AC自动机上每个点为一个未知数,列出方程高斯消元求解即可,时间复杂度$O(n^{3}m^{3})$。
#include<queue> #include<cstdio> #include<algorithm> #define MN 21 #define ld double #define eps 1e-12 using namespace std; struct na{int t[10],bo,f;na(){bo=0;}}t[MN*MN]; int n,m,l,mo[MN],num=0,p[MN],q[MN]; char s[21]; int insert(char s[]){ int i=0,j=0; for (;s[i];j=t[j].t[s[i]-'A'],i++) if (!t[j].t[s[i]-'A']) t[j].t[s[i]-'A']=++num; t[j].bo=1; for (i=0;s[i];i++) if (p[s[i]-'A']==0) return -1; return j; } queue<int> _q; ld a[MN*MN][MN*MN],S; ld abs(ld a){return a<0?-a:a;} inline void Gauss(){ int i,j,k; for (i=0;i<=num;i++){ for (j=i;j<=num;j++) if (abs(a[j][i])>eps) break; for (k=0;k<=num+1;k++) swap(a[i][k],a[j][k]); for (j=0;j<=num;j++) if (j!=i) for (S=a[j][i]/a[i][i],k=0;k<=num+1;k++) a[j][k]-=S*a[i][k]; } } int main(){ register int i,j; scanf("%d%d%d",&n,&l,&m); for (i=0;i<m;i++) scanf("%d%d",&p[i],&q[i]); for (i=0;i<n;i++) scanf("%s",s),mo[i]=insert(s); t[0].f=0; for (i=0;i<m;i++) if (t[0].t[i]) _q.push(t[0].t[i]),t[t[0].t[i]].f=0; while (!_q.empty()){ int k=_q.front();_q.pop(); for (i=0;i<m;i++) if (t[k].t[i]){ for (j=t[k].f;j&&!t[j].t[i];j=t[j].f); t[t[k].t[i]].f=t[j].t[i]; _q.push(t[k].t[i]); } } for (int k=0;k<=num;a[k][k]-=1.,k++) if (!t[k].bo) for (i=0;i<m;i++){ for (j=k;j&&!t[j].t[i];j=t[j].f); j=t[j].t[i]; a[j][k]+=1.*p[i]/q[i]; } a[0][num+1]=-1; Gauss(); for (i=0;i<n;i++) if (mo[i]==-1) puts("0.00");else printf("%.2lf ",a[mo[i]][num+1]/a[mo[i]][mo[i]]+eps); }
第二题,数据范围较大,直接建AC自动机必定会TLE,所以考虑化简。我们需要的只是n个目标状态的答案,而不需要AC自动机上其他点的答案,那么这些点是否可以合并起来,统一考虑呢?
以样例为例:
$S_1=THT,S_2=TTH,S_3=HTT$
考虑一个状态X,表示所有未达到目标状态的字符串。
那么若在X后面接上一个$S_1$,肯定就到达了目标状态,也可能我们还没填完$S_1$我们就达成了目标状态,这些一起考虑起来,可以得到
$$frac{X}{2^{3}}=S_1+frac{S_1}{2^{2}}+frac{S_2}{2}+frac{S_3}{2^{2}}$$
这是什么意思?XTHT有可能是以下几种情况(我们用$F_{S_i}$表示$S_i$的出现概率)
XTHT =$F_{S_1}$
YTHT HT=$F_{S_1}$ HT(X以TH结尾,即X=YTH)
YTTH T=$F_{S_2}$ T(X以T结尾,即X=YT)
YHTT HT=$F_{S_2}$ HT(X以HT结尾,即X=YHT)
额外多出来的x个字符就需要花费$frac{1}{2^{x}}$的概率去生成它,也就很显然地对应了上面的式子。
再考虑在X后面接上$S_2$和$S_3$我们又得到两个方程,再加上$sumF_{S_i}$,一共4(n+1)个方程,解3(n)个变量刚刚好。
另外如果高斯消元的时候你是用的eps来找第一个非0位置,那么很有可能爆精度,这时候你需要将其修改为找到绝对值最大的位置(虽然不知道为什么这样就可以了)。
#include<queue> #include<cstdio> #include<algorithm> #define MN 321 #define ld double #define eps 1e-12 using namespace std; int n,m,l,mo[MN],num=0,ne[MN<<1]; char s[MN][MN],c[MN<<1]; ld a[MN][MN],S,two[MN]; ld abs(ld a){return a<0?-a:a;} inline void Gauss(){ int i,j,k; for (i=0;i<=num;i++){ for (j=i;j<=num;j++) if (abs(a[j][i])>eps) break; for (k=0;k<=num+1;k++) swap(a[i][k],a[j][k]); for (j=0;j<=num;j++) if (j!=i) for (S=a[j][i]/a[i][i],k=0;k<=num+1;k++) a[j][k]-=S*a[i][k]; } } int main(){ register int i,j,k,l; scanf("%d%d",&n,&m); for (i=1;i<=n;i++) scanf("%s",s[i]); for (two[0]=1,i=1;i<=m;i++) two[i]=two[i-1]*0.5; for (i=1;i<=n;i++) for (a[i][0]=-two[m],j=1;j<=n;j++){ for (k=0;k<m;k++) c[k]=s[i][k],c[m+m-k-1]=s[j][m-k-1];c[m+m]=0; ne[0]=-1; for (k=1;k<m+m;ne[k]=l+(c[l+1]==c[k]),k++) for (l=ne[k-1];l!=-1&&c[l+1]!=c[k];l=ne[l]); for (k=ne[k-1];k!=-1;k=ne[k]) if (k<m) a[i][j]+=two[m-1-k]; } for (i=1;i<=n+1;i++) a[0][i]=1; num=n;Gauss(); for (i=1;i<=n;i++) printf("%.8lf ",a[i][n+1]/a[i][i]+eps); }