[SDOI2017]硬币游戏
考虑生成函数来做
g(x)函数就是0+0*x+...+1*x^s+...+|∑|^(n-s)x^n
就是最后s位必须填这个串,但是前面随便填的方案数
然后枚举之前出现了哪个串(包括自己),如果没有相交,就是fj(x)*g(x),还有就是有前后缀有相交部分,
Pji(x)中的第k位,表示i的长度为m-k的前缀和j的长度为m-k的后缀是不是一样 0/1。
再加上最后一个
$\sum f_i=1$因为游戏最终会停止
现在有n+1个方程,g(x)直接当做未知数的话会有交叉项解不出来
把$(1-|\Sigma|x)$乘过去,求导来搞搞事情:
由于带入$x=1/|\Sigma|$使得这个$(1-|\Sigma|x)$等于0,求导可以消去一些项
$-|\Sigma|fi=S(x)^{S-1}-Sx^{S-1}(\sum_jf_j)-x^S(\sum_jf'_j)+|\Sigma|(\sum_jf_jP_{ji})$
把$(\sum_jf'_j)$当做另外一个未知量
就有n+1个未知量,n+1个方程了
代码:
卡精度,不用eps反而更好
// luogu-judger-enable-o2 #include<bits/stdc++.h> #define reg register int #define il inline #define ld long double #define numb (ch^'0') using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=303; const double eps=1e-8; const ll mod[2]={1000000007,998244353}; const ll jin[2]={131,13331}; ll pw[N][2]; ll has[N][N][2]; char s[N]; ld f[N][N]; ld ans[N]; int n,m; ld m0,m1; bool cmp(ld x){ return 1; } void Guass(int n){ for(reg i=1;i<=n;++i){ int id=0; for(reg j=i;j<=n;++j){ if(cmp(f[j][i])&&fabs(f[j][i])>fabs(f[id][i])) id=j; } if(id!=i){ for(reg j=1;j<=n+1;++j){ swap(f[i][j],f[id][j]); } } for(reg j=i+1;j<=n;++j){ if(cmp(f[j][i])){ ld tmp=f[j][i]/f[i][i]; for(reg k=1;k<=n+1;++k){ f[j][k]=f[j][k]-f[i][k]*tmp; } } } } for(reg i=n;i>=1;--i){ for(reg j=1;j<=n;++j){ if(cmp(ans[j])) f[i][n+1]-=ans[j]*f[i][j]; } ans[i]=f[i][n+1]/f[i][i]; } } int main(){ rd(n);rd(m); pw[0][0]=pw[0][1]=1; for(reg i=1;i<=m;++i){ for(reg j=0;j<=1;++j){ pw[i][j]=pw[i-1][j]*jin[j]%mod[j]; } } for(reg i=1;i<=n;++i){ scanf("%s",s+1); for(reg j=1;j<=m;++j){ for(reg l=0;l<=1;++l){ has[i][j][l]=(has[i][j-1][l]*jin[l]+s[j]-'A')%mod[l]; } } } m0=m1=1.0; for(reg i=1;i<=m;++i) m1=m1*0.5; m0=m1*2; for(reg i=1;i<=n;++i){ // cout<<" turns "<<i<<" ----------------------- "<<endl; for(reg j=1;j<=n;++j){ // cout<<" with "<<j<<endl; ld tmp=0.5; ld val=0; for(reg k=1;k<=m-1;++k){ int to=m-k; ll bac0=(has[j][m][0]-has[j][m-to][0]*pw[to][0]%mod[0]+mod[0])%mod[0]; ll bac1=(has[j][m][1]-has[j][m-to][1]*pw[to][1]%mod[1]+mod[1])%mod[1]; if(bac0==has[i][to][0]&&bac1==has[i][to][1]){ // cout<<" ok "<<to<<endl; val+=tmp; } tmp*=0.5; } // cout<<" val "<<val<<endl; f[i][j]=m*m0-2.0*val; } f[i][i]-=2.0; f[i][n+1]=m1; f[i][n+2]=m*m0; } for(reg i=1;i<=n;++i){ f[n+1][i]=1.0; } f[n+1][n+2]=1.0; // for(reg i=1;i<=n+1;++i){ // for(reg j=1;j<=n+2;++j){ // cout<<f[i][j]<<" "; // }cout<<endl; // } Guass(n+1); for(reg i=1;i<=n;++i){ printf("%.10Lf\n",ans[i]); } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/2/18 17:41:21 */
总结:
对于连续一些位置有值的时候,
生成函数确实很好用
求导由于可以降次,这里就把交叉项成功分开了。
PS:这个题也有直接上概率的做法,本质和生成函数相同。但是解释起来最好的绝对是生成函数