[SDOI2017] 硬币游戏

[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)\)没有任何影响

posted @ 2024-04-10 18:53  LuoyuSitfitw  阅读(49)  评论(0)    收藏  举报