BZOJ 4820: [Sdoi2017]硬币游戏

好仙的题目啊,本来是KMP里的题但最后该用的地方被我用Hash艹过去了算了反正这不是这道题的重点

考虑一个暴力的\(O((nm)^3)\)的做法,其实就是BZOJ 1444: [Jsoi2009]有趣的游戏的弱化版,但在这道题中直接上只能得到40pts

我们考虑一下这个方法为什么不行,其实主要的原因是状态实在太多了

考虑到实际上有意义的状态只有\(n\)个,其余的状态都可以合并成一个——“不合法的状态”

乍一看是不是很乱来?怎么能把那么多不同的状态归为一类?

没事我们来找个情况分析一下,在这之前先放一个显然的引理:

引理:构造一个指定的长度为\(l\)\(01\)串的概率是\(\frac{1}{2^l}\)

考虑现在\(S\)是一个不合法的状态(即没有人获胜),\(A=101\)\(B=110\)

那么我们假定游戏进行到到\(S+101\)的状态,那么此时游戏一定会停止,但不一定要等到\(101\)加完才停止

很显然我们可以看出当\(S\)的后缀是\(1\)或者\(10\)时游戏会提前结束,换句话说会有这些情况:

\[S+101=(S+A)+(S'+A+01)+(S''+B+1) \]

其中\(S=S'+10=S''+1\)

那么我们根据上面的引理,可以得到一个方程:

\[\frac{1}{8}S=A+\frac{1}{4}A+\frac{1}{2}B \]

是不是很神奇,但仔细一想我们发现这确实包含了所有情况,那么我们来试着形式化一下这钟方法:

对于每一个字符串\(s_i\),设它获胜的概率是\(p_i\),不合法(即永远无法结束)的辅助状态的概率设为\(S\)

我们发现对于另一个字符串\(s_j\),若\(s_j\)存在长度为\(k\)的后缀能匹配\(s_i\)的前缀,那么就有\(\frac{1}{2^{m-k}}\)的概率提前结束

那么我们在设\(pre_{i,j}\)表示字符串\(s_i\)长度为\(j\)的前缀,\(suf_{i,j}\)表示字符串\(s_i\)长度为\(j\)的后缀

那么对于每一个\(s_i\),我们都将\(S+s_i\)作为一次终止状态列个方程,即:

\[\sum_{j=1}^n\sum_{k=1}^m [pre_{i,k}=suf_{j,k}]\frac{1}{2^{m-k}} p_j=\frac{1}{2^m}S \]

但这样只有\(n\)个方程,但变量有\(n+1\)个。我们发现其实游戏必然会结束,即\(S=0\),所以可以列出最后一个方程:\(\sum_{i=1}^n p_i=1\),然后就上高消即可,复杂度\(O(n^3)\)

PS:那个匹配前缀后缀可以用KMP,但我比较懒因此写了Hash

PPS:这题非常开精度,如果你得到了40pts的好成绩请把高消写成精度更高的方法,并且在BZOJ上由于没有SPJ,因此EPS要调到\(10^{-10}\)

#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
typedef unsigned long long u64;
const int N=305;
const double EPS=1e-10;
struct Hasher
{
    u64 x,y;
    inline Hasher(const u64& X=0,const u64& Y=0) { x=X; y=Y; }
    friend inline bool operator == (const Hasher& A,const Hasher& B)
    {
        return A.x==B.x&&A.y==B.y;
    }
    friend inline Hasher operator + (const Hasher& A,const Hasher& B)
    {
        return Hasher(A.x+B.x,A.y+B.y);
    }
    friend inline Hasher operator * (const Hasher& A,const Hasher& B)
    {
        return Hasher(A.x*B.x,A.y*B.y);
    }
}pw[N],pre[N][N],suf[N][N]; int n,m; double pw2[N],p[N][N],val[N]; char s[N];
const Hasher seed=Hasher(233,167);
inline void Gauss(CI n)
{
    RI i,j,k; for (i=1;i<=n;++i)
    {
        int mx=i; for (j=i;j<=n;++j) if (fabs(p[j][i])>=fabs(p[mx][i]))
        mx=j; swap(p[mx],p[i]); swap(val[mx],val[i]);
        for (j=i+1;j<=n;++j) if (fabs(p[j][i])>=EPS)
        {
            double dv=1.0*p[j][i]/p[i][i]; val[j]-=val[i]*dv;
            for (k=i;k<=n;++k) p[j][k]-=p[i][k]*dv;
        }
    }
    for (val[n]/=p[n][n],i=n-1;i;--i)
    {
        for (j=i+1;j<=n;++j) val[i]-=p[i][j]*val[j];
        val[i]/=p[i][i];
    }
}
int main()
{
    RI i,j,k; for (scanf("%d%d",&n,&m),pw[0]=Hasher(1,1),pw2[0]=i=1;i<=m;++i)
    pw[i]=pw[i-1]*seed,pw2[i]=pw2[i-1]*0.5; for (i=1;i<=n;++i)
    {
        for (scanf("%s",s+1),j=1;j<=m;++j) pre[i][j]=(pre[i][j-1]*seed)+Hasher(s[j],s[j]);
        for (j=m;j;--j) suf[i][j]=suf[i][j+1]+(Hasher(s[j],s[j])*pw[m-j]);
    }
    for (i=1;i<=n;++i) for (j=1;j<=n;++j) for (k=1;k<=m;++k)
    if (pre[i][k]==suf[j][m-k+1]) p[i][j]+=pw2[m-k];
    for (i=1;i<=n;++i) p[i][n+1]=-pw2[m],p[n+1][i]=1; val[n+1]=1;
    for (Gauss(n+1),i=1;i<=n;++i) printf("%.10lf\n",val[i]);
    return 0;
}
posted @ 2020-01-31 18:57  空気力学の詩  阅读(157)  评论(0编辑  收藏  举报