[DarkBZOJ2688] Green Hackenbush
前言
关于水了一篇博客还想再水一篇博客但是又必须想点什么理由这件事。
题目
讲解
首先声明:这道题的二叉树左右子树独立,比如左儿子有一条链和右儿子有一条链是两种不同的树。
所以我们就可以轻松得出 \(n\) 个点的二叉树的棵数公式:\(cat_{n}=\sum_{i=0}^{n-1} cat_{i}\times cat_{n-i-1}\)
如果你把Catalan的各种形式背得滚瓜烂熟或者你发现了我变量的取名特点,其实你早就可以发现这就是Catalan数。
当然你不知道也没关系,只需要知道的是这道题这玩意可以用double存下来就好。
我们要求的当然就是每棵树的SG值的异或和大于零的概率,对于一棵树,SG值有个经典结论,可以去上一篇博客看(找到水两篇博客的理由了!)。
然后直接令 \(f_{i,j}\) 表示 \(i\) 个点的树,SG值为 \(j\) 的概率,转移详见代码,注意单个儿子的情况。
然后令 \(dp_{i,j}\) 表示前 \(i\) 棵树的SG值异或和为 \(j\) 的概率,最后答案就是 \(1-dp_{i,j}\)。
时间复杂度来到了玄学的 \(O(2^{14}n^2)\)。
代码
//12252024832524
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 305;
int n;
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}
double cat[MAXN],f[MAXN][MAXN];//i : the number of points,j : xor
double dp[MAXN][MAXN];
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
cat[0] = cat[1] = 1; cat[2] = 2;
for(int i = 3;i <= 127;++ i)
for(int j = 0;j < i;++ j)
cat[i] += cat[j] * cat[i-j-1];
f[1][0] = 1;
for(int i = 2;i <= 100;++ i)
{
for(int j = 0;j <= 127;++ j) f[i][j+1] += 2.0 * f[i-1][j] * cat[i-1];
for(int c = 1;c < i-1;++ c)
for(int j = 0;j <= 127;++ j)
for(int k = 0;k <= 127;++ k)
f[i][(j+1)^(k+1)] += cat[c] * f[c][j] * cat[i-c-1] * f[i-c-1][k];
for(int j = 0;j <= 127;++ j) f[i][j] /= cat[i];
}
n = Read();
int val = Read();
for(int i = 0;i <= 127;++ i) dp[1][i] = f[val][i];
for(int i = 2;i <= n;++ i)
{
val = Read();
for(int j = 0;j <= 127;++ j)
for(int k = 0;k <= 127;++ k)
dp[i][j^k] += dp[i-1][j] * f[val][k];
}
printf("%.6f\n",1-dp[n][0]);
return 0;
}