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}^nsum_{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
",val[i]);
    return 0;
}
原文地址:https://www.cnblogs.com/cjjsb/p/12246269.html