CF 618G

1 篇文章 0 订阅

题目大意

一开始有 N 个空位。给定一个概率P。每次你有 P 的概率在第N个空位加入一个 1 ,有1P的概率加入一个 2 。加入之后,你会将这个数字往前推,假如当前数字遇到了另外一个和他值一样的数字,设为v,那么当前空位的数字变为 v+1 ,并且继续往前推。直到到达边界或遇到一个不一样的数字。游戏结束当且仅当所有空位都放了数字。一种结束局面的权值定义为所有数字的和。问你最终结束时局面的期望权值。

数据范围

N109
109P1 ,且 P 的精度范围为109

题解

Ai,j 表示当前有 i 个格子,能够凑出数字j的概率,注意只能能凑出,没有保证最终一定是。那么很显然的, Ai,j=Ai,j1Ai1,j1

Bi,j 的意义是一样的。只是保证在一开始放的数字是2。那么
Bi,j=Bi,j1Ai1,j1

那么我们再做些更正,即 Ai,j,Bi,j 的意义变为第 i 个恰好为j的概率。那么
Ai,j=Ai,j(1Ai1,j) A 是上面求的 A

而且可以发现的一点是,当j很大时, Ai,j 就趋近于0了。因为他大概是以平方级别变化,那么事实上当 j50 时就等于0了。
还有一点是,当 i 比较大时,Ai,j=Ai1,j,分析方法同理。那么事实上,只需要预处理出 1i50 的值就好了。

接来下考虑怎么求答案。

dpi,j 表示当前有 i 个格子,第一个格子的值为j,且不会与其他数字结合的期望权值。我们对 j 进行分类讨论。

  1. j=1
    那么只需要第二个格子不是1就好了。可以得到递推式

    dpi,j=j+50k=2dpi1,kBi1,k50k=2Bi1,k

    这就是为什么我们要预处理出 B ,因为我们第一个塞进来的不能是1,否则就合并了。
    而且递推式的分母其实就是出现这种情况的概率。

    2.j>1
    可以发现的是,第二个格子的值必然比 j 要小,不然的话肯定会发生合并。那么我们就可以得到另一条式子了

    dpi,j=j+j1k=1dpi1,kAi1,kj1k=1Ai1,k

    但这题到这里还没结束,因为 N109 !
    但之前说了,由于 j50 而且当 N>50 AN,j=AN1,j ,也就是说,我们只需要求出 dp1>50 ,对于 N>50 的,每次转移的系数都是一样的!那么我们就可以构出转移的矩阵,直接转移就好了。

    那么我们最终的答案就是

    j=150dpN,jAN,j

    代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int MAXN = 55;
    
    typedef double Matrix[MAXN][MAXN];
    
    Matrix A,B,Trans,Dp,Cur;
    double Out[55];
    int N;
    
    void Mul(Matrix &a,Matrix &b,Matrix &c)
    {
        static Matrix Tmp;
        memset(Tmp,0,sizeof Tmp);
        for(int i = 0;i <= 50;i ++)
            for(int k = 0;k <= 50;k ++)
                for(int j = 0;j <= 50;j ++)
                    Tmp[i][j] += a[i][k] * b[k][j];
        memcpy(c,Tmp,sizeof c);
    }
    
    int main()
    {
    //  freopen("data.in","r",stdin),freopen("data.out","w",stdout);
        double p,q;
        scanf("%d%lf", &N, &p);
        p = p / (1e9),q = 1 - p;
        for(int i = 1;i <= 50;i ++)
            for(int j = 1;j <= 50;j ++)
            {
                if (j == 1) A[i][j] += p;
                if (j == 2) A[i][j] += q,B[i][j] += q;
                A[i][j] += A[i][j - 1] * A[i - 1][j - 1];
                B[i][j] += B[i][j - 1] * A[i - 1][j - 1];
            }
        for(int i = 50;i;i --)
            for(int j = 1;j <= 50;j ++)
                A[i][j] *= (1 - A[i - 1][j]),B[i][j] *= (1 - A[i - 1][j]);
        for(int j = 1;j <= 50;j ++) Dp[1][j] = j;
        for(int i = 2;i <= 50;i ++)
            for(int j = 1;j <= 50;j ++)
            {
                double s = 0;
                if (j == 1)
                {
                    for(int k = 2;k <= 50;k ++) Dp[i][j] += Dp[i - 1][k] * B[i - 1][k],s += B[i - 1][k];
                } else
                    for(int k = 1;k < j;k ++) Dp[i][j] += Dp[i - 1][k] * A[i - 1][k],s += A[i - 1][k];
                Dp[i][j] /= s;
                Dp[i][j] += j;
            }
        Trans[0][0] = 1;
        for(int j = 1;j <= 50;j ++) Trans[0][j] = j;
        //For j = 1
        double s = 0;
        for(int k = 2;k <= 50;k ++) s += B[50][k];
        for(int k = 2;k <= 50;k ++) Trans[k][1] += B[50][k] / s;
        //For j not equal 1
        for(int j = 2;j <= 50;j ++)
        {
            s = 0;
            for(int k = 1;k < j;k ++) s += A[50][k];
            for(int k = 1;k < j;k ++) Trans[k][j] += A[50][k] / s;
        }   
        int bk = N;
        if (N > 50)
        {
            memcpy(Cur[0],Dp[50],sizeof Cur);
            Cur[0][0] = 1;
            for(N -= 50;N;N >>= 1)
            {
                if (N & 1) Mul(Cur,Trans,Cur);
                Mul(Trans,Trans,Trans);
            }
            memcpy(Out,Cur[0],sizeof Out);
        } else memcpy(Out,Dp[N],sizeof Out);
        double ans = 0;
        for(int j = 1;j <= 50;j ++) ans += Out[j] * A[bk > 50 ? 50 : bk][j];
        printf("%.15f\n", ans);
        return 0;
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 两个人在一棵树上玩井字棋游戏,其中一个人是X,另一个人是O。游戏一开始是空的,每轮玩家可以在一个空格子里放置他的符号,当出现任意一方形成了一条长度为3的线(包括水平、竖直、对角线),这个人就赢了。 你需要编写一个程序来判断游戏是否结束,如果游戏结束,你需要输出胜利者的符号('X'或'O'),如果游戏没有结束,你需要输出 "Draw"。 输入格式 第一行包含一个整数 n,表示树的节点的个数。 接下来 n-1 行每行描述一条边,包含两个整数 ui 和 vi,表示树上连接了节点 ui 和节点 vi。 输出格式 如果游戏结束,输出胜利者的符号('X'或'O'),否则输出 "Draw"。 数据范围 1 ≤ n ≤ $10^5$ 输入样例1 6 1 2 1 3 2 4 2 5 3 6 输出样例1 Draw 输入样例2 6 1 2 1 3 2 4 2 5 4 6 输出样例2 O 题目分析 树上博弈论,我们可以考虑用 SG 函数来做,但是 SG 函数的计算比较麻烦,需要考虑子树的 SG 值,我们考虑另外一种方式,直接判断胜负。 对于一个节点 u,我们先 DFS 计算节点 u 的子树中 X 和 O 的个数,设 X 数量为 cntx,O 数量为 cnto。那么对于其父亲节点 v,v 节点的胜负状态有以下几种情况: - 如果 u 节点下有一颗子树中有三个 X,或者有三个 O,那么当前局面就结束了,胜利者为这颗子树中的 X 或 O。 - 如果 u 节点下有一颗子树中有两个 X,或者有两个 O,那么 u 和 u 的子树中除了这颗子树外的所有子树都可以随意选择 X 或 O,所以这颗子树中的 X 或 O 不会影响最终结果,当前节点 v 的胜负状态和其它子树相同。 - 如果 u 节点下有一颗子树中有一个 X,或者有一个 O,那么 u 和 u 的子树中除了这颗子树外的所有子树都可以随意选择 X 或 O,所以这颗子树中的 X 或 O 不会影响最终结果,当前节点 v 的胜负状态取决于其它子树的胜负状态。 时间复杂度 $O(n)$ C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值