• 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;
    }
    
  • 相关阅读:
    指针总结与地址
    寻址方式
    为什么要有指针?
    指针与变量
    Wired Memory
    Memory Usage Performance Guidelines
    内存管理与运行时
    Java jvm 内存回收机制
    GC详解及Minor GC和Full GC触发条件总结
    Java的内存回收机制详解X
  • 原文地址:https://www.cnblogs.com/cjjsb/p/12246269.html
Copyright © 2020-2023  润新知