[BZOJ2688]Green Hackenbush
[BZOJ2688]Green Hackenbush
题意: 有\(n\)棵随机的二叉树,每棵只知道大小为\(a_i\)
博弈:每次选取一个子树删掉,只剩根不能操作,求先手获胜概率
考虑这个博弈,求出一棵树的\(\text{SG}\)值
显然有:
1.只有一个点的树的\(\text{SG}\)值为0
2.多个树组合的问题为\(\text{SG}\)值异或
暴力\(dp\),对于树\(T\)求答案,设\(T\)所有可行的后继状态集合为\(N(T)\),则得到\(\text{SG}\)值的表达式为
$\text{SG}(T)=\text{mex}_{R\in N(T)}\lbrace\text{SG(R)}\rbrace $
直接求解复杂度过高,考虑归纳性质
性质:
1.一棵根节点只有一个儿子的树,其\(\text{SG}\)值为儿子的\(\text{SG}\)值+1
考虑归纳证明:
设子树为\(T\),令\(T+u\)表示\(T\)子树上面接上自己作为根,问题变为求证\(\text{SG}(T+u)=\text{SG}(T)+1\)
设已经归纳证明所有\(T\)的子联通块成立
我们要求\(\text{SG}(T+u)\)
\(\text{SG}(T+u)=\text{mex}\{\text{SG}(u),\forall _{R\in N(T)}\text{SG}(R+u)\}\)
由归纳的性质有
\(\forall _{R\subsetneq T}\text{SG}(R+T)=\text{SG}(R)+1\)
又因为\(\text{SG}(u)=0\),看做把所有儿子的情况平移了1,0的位置由自己占据,因而上式成立
2.多叉树的问题可以归纳为 根分别接上每个儿子得到的树 的问题的组合
因为儿子之间实际互不干扰,比较容易理解
由此得到,一棵树的\(\text{SG}\)值为其所有儿子的\(\text{SG}\)值+1的异或和
令\(dp_{n,i}\)为一棵\(n\)个节点的二叉树\(\text{SG}\)值为\(i\)的概率,为了便于转移,设空树的\(\text{SG}\)值为-1
考虑直接枚举两棵子树的大小和\(\text{SG}\)值
考虑对于\(n\)个节点的二叉树,设其左儿子为\(i\)时的总概率为\(F_i\)
得到的\(\text{dp}\)转移是
\(dp_{n,(a+1)\oplus (b+1)}\leftarrow {dp_{i,a}\cdot dp_{n-i-1,b}\cdot F_i}\)
我们知道\(n\)个节点的二叉树方案数为\(Catalan(n)=\frac{(2n)!}{n!(n+1)!}\)
由此得到$\begin{aligned} F_i=\frac{Catalan(i)Catalan(n-i-1)}{Catalan(n)}\end{aligned} $
此题范围可以直接带入\(Catalan(i)\)求解,但是依然要提一下递推的做法(似乎精度更有保障?)
$\begin{aligned} F_i=\frac{\frac{(2i)!}{i!(i+1)!}\cdot \frac{(2n-i-2)!}{(n-i-1)!(n-i)!}}{\frac{(2n)}{n!(n+1)!}}\end{aligned} $
递推求解\(F_i\),每次\(i\)改变一阶乘只会改变1或者2,因此由\(F_{i-1}\)得到\(F_i\)的递推式为
\(F_i=\left\{ \begin{aligned}\frac{n(n+1)}{2n(2n-1)}&& i=0\\ F_{i-1}\cdot \frac{2i(2i-1)}{(i+1)i}\frac{(n-i+1)(n-i)}{2(n-i)(2n-2i-1)} && i\in[1,n-1]\end{aligned}\right.\)
化简之后应该是
\(F_i=\left\{ \begin{aligned}\frac{(n+1)}{2(2n-1)}&& i=0\\ F_{i-1}\cdot \frac{(2i-1)}{(i+1)}\frac{(n-i+1)}{(2n-2i-1)} && i\in[1,n-1]\end{aligned}\right.\)
至此得到一个朴素的\(O(n^4)\)预处理,由于是异或,可以用\(\text{FWT}_{\oplus}\)求解,复杂度为\(O(n^3)\)
对于输入的每棵树,类似背包地叠加概率即可,复杂度为\(O(n^3)\)
以下是朴素dp代码
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=128;
int n;
db dp[N][N];
void FWT(db *a,int f){
for(int i=1;i<N;i<<=1){
for(int l=0;l<n;l+=i*2) {
for(int j=l;j<l+i;j++){
db t=a[j+i];
a[j+i]=a[j]-t;
a[j]+=t;
}
}
}
if(f==-1) rep(i,0,N-1) a[i]/=N;
}
db F[N],G[N];
int main(){
dp[0][0]=1,dp[1][0]=1;
rep(i,2,100) {
F[0]=1.0/(2*i)/(2*i-1)*(i+1)*i;
rep(j,1,i-1) {
F[j]=F[j-1] * (2*j)*(2*j-1)/(j+1)/j * 1.0/(2*(i-j))/(2*(i-j)-1)*(i-j+1)*(i-j);
}
rep(a,0,i-1) rep(h1,0,N-1) if(dp[a][h1]>0) {
rep(h2,0,N-1) if(dp[i-a-1][h2]) {
int nxt=0;
if(a>0) nxt^=h1+1;
if(i-1-a>0) nxt^=h2+1;
dp[i][nxt]+=dp[a][h1]*dp[i-a-1][h2]*F[a];
}
}
}
n=rd();
rep(i,0,N-1) F[i]=0;
F[0]=1;
rep(i,1,n) {
int x=rd();
rep(j,0,N-1) G[j]=0;
rep(j,0,N-1) if(F[j]) rep(k,0,N-1) G[j^k]+=F[j]*dp[x][k];
rep(j,0,N-1) F[j]=G[j];
}
db ans=0;
rep(i,1,N-1) ans+=F[i];
printf("%.6lf\n",ans);
}
以下是FWT优化代码
#include<bits/stdc++.h>
using namespace std;
typedef double db;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)
char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}
const int N=128;
int n;
db dp[N][N],T[N][N];
void FWT(db *a,int f){
for(int i=1;i<N;i<<=1){
for(int l=0;l<N;l+=i*2) {
for(int j=l;j<l+i;j++){
db t=a[j+i];
a[j+i]=a[j]-t;
a[j]+=t;
}
}
}
if(f==-1) rep(i,0,N-1) a[i]/=N;
}
db F[N],G[N];
int main(){
dp[0][0]=1,dp[1][0]=1;
T[0][0]=1,T[1][1]=1;
FWT(T[0],1),FWT(T[1],1);
rep(i,2,100) {
F[0]=1.0/(2*i)/(2*i-1)*(i+1)*i;
rep(j,1,i-1) {
F[j]=F[j-1] * (2*j)*(2*j-1)/(j+1)/j * 1.0/(2*(i-j))/(2*(i-j)-1)*(i-j+1)*(i-j);
}
rep(j,0,i-1) rep(k,0,N-1) dp[i][k]+=T[j][k]*T[i-j-1][k]*F[j];
FWT(dp[i],-1);
rep(j,0,N-2) T[i][j+1]=dp[i][j];
FWT(T[i],1);
}
n=rd();
rep(i,0,N-1) F[i]=0;
F[0]=1;
rep(i,1,n) {
int x=rd();
rep(j,0,N-1) G[j]=0;
rep(j,0,N-1) if(F[j]) rep(k,0,N-1) G[j^k]+=F[j]*dp[x][k];
rep(j,0,N-1) F[j]=G[j];
}
db ans=0;
rep(i,1,N-1) ans+=F[i];
printf("%.6lf\n",ans);
}