bzoj2688 Green Hackenbush(博弈+概率dp)

本文详细解析了一款基于树结构的博弈游戏算法,通过计算不同形态的二叉树及其子树的SG值来确定游戏的胜负概率。文章介绍了Catalan数的应用以及如何通过动态规划求解每棵树可能的异或值概率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

  有一个古老的游戏叫做Green Hackenbush,游戏是这样进行的:两个人轮流在一棵树上删边,每次删边后不与根联通的子树直接被ignore,不能删边的游戏者输。Alice和Bob也在玩这个游戏,不过他们面对的是n棵树,第i棵树是含有a[i]个节点的二叉树。先手的Alice想知道自己有多大的概率获胜(假设我们的Alice和Bob同学都是无限聪明的)。

Input

  第一行一个数n。

  接下来每行一个数a[i]。
 

Output

  一个保留6位小数的实数ans。
 

Sample Input

1

2

Sample Output

1.000000

HINT

  对于100%的数据,n<=100,a[i]<=100

Source

[ Submit][ Status][ Discuss]


分析:
经典博弈: 树上删边游戏
结点的SG值为( ta的所有子节点的SG值加1)的异或和
那么对于一片森林来说,Nim和就是所有树的异或和
如果Nim和为0,那么先手必败,否则先手必胜

但是现在我们不知道树的形态(只知道是二叉树),所以这里就需要概率帮忙了
我们考虑一棵 n n 个节点的二叉树有多少种形态,左右子树分开考虑

h[n]=i=0n1h[i]h[n1i]

Catalan数熟悉的同学就可以看出,结点个数为n的不同形态二叉树个数 Catalan(n) C a t a l a n ( n )

C(n+1)=4n+2n+2C(n) C ( n + 1 ) = 4 n + 2 n + 2 C ( n )

f[i][j] f [ i ] [ j ] 表示一棵 i i 节点的二叉树,异或值为j的概率
还是需要利用左右子树分开考虑来做

f[n][(x+1)xor(y+1)]=n1i=0(h[i]f[i][x])(h[n1i]f[n1i][y]) f [ n ] [ ( x + 1 ) x o r ( y + 1 ) ] = ∑ i = 0 n − 1 ( h [ i ] ∗ f [ i ] [ x ] ) ∗ ( h [ n − 1 − i ] ∗ f [ n − 1 − i ] [ y ] )
h[i]f[i][x] h [ i ] ∗ f [ i ] [ x ] i i 个结点的子树的形态个数为h[i]
最后: f[n][i]/=h[n] f [ n ] [ i ] / = h [ n ]

g[i][j] g [ i ] [ j ] 表示的是前 i i 棵子树异或值为j的概率

g[i][j(xor)k]=g[i1][j]f[a[i]][k] g [ i ] [ j ( x o r ) k ] = g [ i − 1 ] [ j ] ∗ f [ a [ i ] ] [ k ]

最后的答案就是 1f[n][0] 1 − f [ n ] [ 0 ]

tip

卡特兰数太大了会不会炸掉啊
但其实用double做的话是没什么问题的= =
好像是因为浮点数什么神奇的运算?

注意只有一个子结点的情况:

for (int j=0;j<=127;j++) f[i][j+1]+=2.0*h[i-1]*f[i-1][j];

子树的大小为i-1,子数的SG值为j: f[i1][j] f [ i − 1 ] [ j ]
大小为i-1的二叉树形态个数为 h[i1] h [ i − 1 ] 2 ∗ 2 :到底是左子树还是右子树
那么根结点的SG值就等于 j+1 j + 1

100个结点,最大的异或值为127

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N=130;
const int Max=127;     //最大异或值 
int n,a[N];
double f[N][N],g[N][N],h[N];

int main()
{
    int mx=0;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]),mx=max(mx,a[i]);

    h[0]=1.0; h[1]=1.0; h[2]=2.0;
    for (int i=3;i<=127;i++)
        for (int j=0;j<i;j++)
            h[i]+=h[j]*h[i-j-1];        //Catalan数递推式 

    f[1][0]=1.0;                        //一个结点的SG值为0 
    for (int i=2;i<=mx;i++) {
        for (int j=0;j<=127;j++) f[i][j+1]+=2.0*h[i-1]*f[i-1][j];    //只有一个子结点 
        for (int j=1;j<i-1;j++)         //左子树大小 
            for (int x=0;x<=127;x++)
                for (int y=0;y<=127;y++) {
                    f[i][(x+1)^(y+1)]+=f[j][x]*h[j]*f[i-j-1][y]*h[i-j-1];
                }
        for (int j=0;j<=127;j++) f[i][j]/=h[i];     //总概率/方案数 
    }

    for (int i=0;i<=127;i++) g[1][i]=f[a[1]][i];
    for (int i=2;i<=n;i++)
        for (int j=0;j<=127;j++)
            for (int k=0;k<=127;k++)
                g[i][j^k]+=g[i-1][j]*f[a[i]][k];

    printf("%.6lf\n",1.0-g[n][0]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值