[SDOI2017] 硬币游戏
还是因为感觉网上的写的不够清晰,所以来写一篇
用\(P(i)\)表示第\(i\)个同学胜利的概率,\(s_i\)表示第\(i\)个同学猜的序列
可以发现,第\(i\)个同学胜利当且仅当当前硬币序列\(T\)的后\(m\)位刚好为\(s_i\),且\(T\)除了最后\(m\)位出现过\(s_i\),其他任何位置都没有出现过任何一个同学猜的序列
那么这样的\(T\)一定可以表示为\(S+s_i\),其中\(S\)是一个不合法的硬币序列
但可以发现,不一定所有的\(S+s_i\)都是合法的\(T\),因为可能出现\(S\)的后\(k\)个和\(s_i\)的前\(m-i\)个刚好构成了某一个\(s_j\)
不过好在,这种情况不算复杂,这样的\(S+s_i\)其实就相当于是\(S'+s_j+s'_i\),其中\(S'\)为\(S\)去掉后\(k\)个字符后的序列,\(s'_i\)为\(s_i\)去掉了前\(m-i\)个字符后的序列
那么显然可以发现,若\(k\)取最大,\(S'+s_j\) 一定是一个合法的\(T\)
eg:
\(\{s\}\)为\(abc,bcd,cda\)
当前序列为\(Q+a+b+s_3\),其中\(S=Q+a+b\)
那么\(k\)不用取最大时,可取\(1\)或\(2\),此时显然当\(k\)取\(1\)时的\(S'\)为\(Q+a\),\(k\)取\(2\)时的\(S'\)为\(Q\)
可以发现\(k\)取\(1\)时的\(S'+s_2\)不是一个合法的\(T\),因为里面还包含了\(s_1\),而\(k\)取\(2\)时显然是合法的
而当\(k\)取最大时,可以发现对于同一个\(i\),每种不合法的\(T\)都和某个\(j\)一一对应,也就是说,当前不合法的\(T\)是由一个\(j\)的某一个合法的\(T\)加上\(i\)的某一个后缀,使得最后\(m\)个字符恰好等于\(s_i\)
这样就可以做到不重不漏的得到所有\(i\)对应的所有不合法的\(T\)
设\(X\)为得到合法或不合法的\(T\)的概率和,则\(\frac{X}{2^{m}}\)为得到某个特定的\(s_i\)的\(T\)的概率
则有转移:
\[\forall i,\frac{X}{2^{m}}=p(i)+\sum_{j=1\&\&j\neq i}^{n}\sum_{k=1}^m[pre_{i,k}==suf_{j,k}]\frac 1{2^{m-k}}p(j) \]这里的\(\frac1{2^{m-k}}\)是因为得到的\(S+s_i\)是\(S'+s_j\)后再加上\(m-k\)个指定的字符
因为我们的未知数有\(X\),\(p(i)\)共\(n+1\)个,所以还需再加上一个\(\sum p(i)=1\)就可以解出所有未知数了
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N=305,P=131;
const double eps=1e-6;
int n,m;
char s[N][N];
ull pre[N][N],suf[N][N],pw[N];
double a[N][N],pw2[N];
int cmp(double x){ return fabs(x)<eps?0:x<0?-1:1; }
void guass(int n){
for(int i=1,p;i<=n;++i){
p=i;
for(int j=i+1;j<=n;++j) if(cmp(a[j][i]-a[p][i])>0) p=j;
if(i^p) swap(a[i],a[p]);
for(int j=1;j<=n;++j) if(i^j){
double v=-a[j][i]/a[i][i];
for(int k=1;k<=n+1;++k) a[j][k]+=v*a[i][k];
}
}
}
int main(){
scanf("%d%d",&n,&m);
pw[0]=1; for(int i=1;i<=m;++i) pw[i]=pw[i-1]*P;
for(int i=1;i<=n;++i){
scanf("%s",s[i]+1);
for(int j=1;j<=m;++j) pre[i][j]=pre[i][j-1]*P+s[i][j];
for(int j=m;j;--j) suf[i][j]=suf[i][j+1]+s[i][j]*pw[m-j];
}
pw2[0]=1; for(int i=1;i<=m;++i) pw2[i]=pw2[i-1]/2.;
for(int i=1;i<=n;++i){
a[i][n+1]=-pw2[m];
for(int j=1;j<=n;++j)
for(int k=1;k<=m;++k)
if(pre[i][k]==suf[j][m-k+1]) a[i][j]+=pw2[m-k];
}
for(int i=1;i<=n;++i) a[n+1][i]=1;
a[n+1][n+2]=1;
guass(n+1);
for(int i=1;i<=n;++i) printf("%.6lf\n",a[i][n+2]/a[i][i]);
return 0;
}
因为我们并不关心\(X\)的具体值,且所有\(a[i][n+1](i\in[1,n])\)是相同的,则在高斯消元时,\(X\)的系数不会影响\(p(i)\)的取值,所以可以在第\(32\)行的\(a[i][n+1]=-pw2[m]\)赋值处直接附任意一个值,只需要保证对于任意一个\(a[i][n+1](i\in[1,n])\)都是相同的值即可
或者说可以这样理解:我们假如已经求出了真正的\(X\)值,那么这时我们将方程中所有\(X\)的系数乘上同一个值\(k\),则为了保持原式依旧成立,\(X\)的值要乘上\(\frac 1k\),因为\(n+1\)个式子就可以唯一确定\(n+1\)元一次多项式,那么新的高斯消元求出来的就是这个新的\(X\),明显其的系数对\(p(i)\)没有任何影响
标签:frac,游戏,硬币,int,可以,合法,SDOI2017,序列 From: https://www.cnblogs.com/LuoyuSitfitw/p/18127176