[BZOJ2688]Green Hackenbush
题意: 有(n)棵随机的二叉树,每棵只知道大小为(a_i)
博弈:每次选取一个子树删掉,只剩根不能操作,求先手获胜概率
考虑这个博弈,求出一棵树的( ext{SG})值
显然有:
1.只有一个点的树的( ext{SG})值为0
2.多个树组合的问题为( ext{SG})值异或
暴力(dp),对于树(T)求答案,设(T)所有可行的后继状态集合为(N(T)),则得到( ext{SG})值的表达式为
$ ext{SG}(T)= ext{mex}_{Rin N(T)}lbrace ext{SG(R)} brace $
直接求解复杂度过高,考虑归纳性质
性质:
1.一棵根节点只有一个儿子的树,其( ext{SG})值为儿子的( ext{SG})值+1
考虑归纳证明:
设子树为(T),令(T+u)表示(T)子树上面接上自己作为根,问题变为求证( ext{SG}(T+u)= ext{SG}(T)+1)
设已经归纳证明所有(T)的子联通块成立
我们要求( ext{SG}(T+u))
( ext{SG}(T+u)= ext{mex}{ ext{SG}(u),forall _{Rin N(T)} ext{SG}(R+u)})
由归纳的性质有
(forall _{Rsubsetneq T} ext{SG}(R+T)= ext{SG}(R)+1)
又因为( ext{SG}(u)=0),看做把所有儿子的情况平移了1,0的位置由自己占据,因而上式成立
2.多叉树的问题可以归纳为 根分别接上每个儿子得到的树 的问题的组合
因为儿子之间实际互不干扰,比较容易理解
由此得到,一棵树的( ext{SG})值为其所有儿子的( ext{SG})值+1的异或和
令(dp_{n,i})为一棵(n)个节点的二叉树( ext{SG})值为(i)的概率,为了便于转移,设空树的( ext{SG})值为-1
考虑直接枚举两棵子树的大小和( ext{SG})值
考虑对于(n)个节点的二叉树,设其左儿子为(i)时的总概率为(F_i)
得到的( ext{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)!})
由此得到$egin{aligned} F_i=frac{Catalan(i)Catalan(n-i-1)}{Catalan(n)}end{aligned} $
此题范围可以直接带入(Catalan(i))求解,但是依然要提一下递推的做法(似乎精度更有保障?)
$egin{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{ egin{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)} && iin[1,n-1]end{aligned} ight.)
化简之后应该是
(F_i=left{ egin{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)} && iin[1,n-1]end{aligned} ight.)
至此得到一个朴素的(O(n^4))预处理,由于是异或,可以用( ext{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
",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
",ans);
}