BZOJ2688 Green Hackenbush <概率DP+博弈论>

Problem

Green Hackenbush

Time  Limit:  10  Sec\mathrm{Time\;Limit:\;10\;Sec}
Memory  Limit:  128  MB\mathrm{Memory\;Limit:\;128\;MB}

Description

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

Input

第一行一个数nn
接下来每行一个数aia_i

Output

一个保留66位小数的实数ansans

Sample Input

1
2
1
2

Sample Output

1
1.000000

HINT

对于100%100\%的数据,n100n\le100ai100a_i\le100

Source

CodeCraft09

标签:概率DP 博弈论

Solution

概率DP\mathrm{DP}与博弈论的结合,需要用到克朗原理(Colon  Principle\mathrm{Colon\;Principle}),即在树上删边游戏中,一个结点的SG\mathrm{SG}值等于其儿子的SG\mathrm{SG}值加11后的异或和。
hih_i表示结点数为ii的二叉树的形态种数,那么有状态转移方程:hi=j=0i1hjhij1h_i=\sum_{j=0}^{i-1}h_jh_{i-j-1}。边界:h0=1h_0=1
gi,xg_{i,x}表示结点数为ii的二叉树根节点SG\mathrm{SG}值为xx的期望。由于只有100100个结点,可知SG\mathrm{SG}值最大为127127。于是有状态转移:(刷表法)做到ii个结点的情况时,枚举两棵子树的大小j,kj,k,保证j+k=i1j+k=i-1j,k[1,i)j,k\in[1,i),枚举两棵子树的SG\mathrm{SG}x,y[0,127]x,y\in[0,127],那么其对gi,(x+1)(y+1)g_{i,(x+1)\oplus(y+1)}的贡献为hjgj,xhkgk,yhi\frac{h_jg_{j,x}\cdot h_kg_{k,y}}{h_i}。边界:g1,0=1g_{1,0}=1
fi,xf_{i,x}表示第1i1\sim i棵树SG\mathrm{SG}值异或和为xx的期望。同样用刷表法,做到1i1\sim i棵树时,枚举第ii棵树的SG\mathrm{SG}x[0,127]x\in[0,127],枚举前1(i1)1\sim(i-1)棵树SG\mathrm{SG}值异或和为y[0,127]y\in[0,127],那么其对fi,xyf_{i,x\oplus y}的贡献为fi1,ygai,xf_{i-1,y}\cdot g_{a_i,x}。边界:f1,x=ga1,x  (x[0,127])f_{1,x}=g_{a_1,x}\;(x\in[0,127])
按顺序DP\mathrm{DP}h,g,fh,g,f即可求解,显然当SG\mathrm{SG}值不等于00Alice\mathrm{Alice}获胜,于是答案为1fn,01-f_{n,0}

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>
using namespace std;
typedef double dnt;
template <class T> inline void read(T &x) {
x = 0; int c = getchar(), f = 1;
for (; !isdigit(c); c = getchar()) if (c == 45) f = -1;
for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0');
}
int n, a[105];
dnt f[105][130], g[105][130], h[105];
int main() {
read(n); g[1][0] = h[0] = 1.0;
for (int i = 1; i <= n; i++) read(a[i]);
for (int i = 1; i <= 100; i++)
for (int j = 0, k = i-1; j < i; j++, k--)
h[i] += h[j]*h[k];
for (int i = 2; i <= 100; i++) {
for (int x = 0; x <= 127; x++)
g[i][x+1] += 2.0*g[i-1][x]*h[i-1]/h[i];
for (int j = 1, k = i-2; j < i-1; j++, k--)
for (int x = 0; x <= 127; x++)
for (int y = 0; y <= 127; y++)
g[i][(x+1)^(y+1)] += g[j][x]*g[k][y]*h[j]*h[k]/h[i];
}
for (int x = 0; x <= 127; x++) f[1][x] = g[a[1]][x];
for (int i = 2; i <= n; i++)
for (int x = 0; x <= 127; x++)
for (int y = 0; y <= 127; y++)
f[i][x^y] += f[i-1][x]*g[a[i]][y];
return printf("%.6lf\n", 1.0-f[n][0]), 0;
}