[SHOI2012] 随机树 题解
[SHOI2012] 随机树 题解
题意分析
十分清楚,无需分析。
思路
40pts
两个 \(n \le 10\) 的部分分可以直接爆搜。
#include<bits/stdc++.h>
#define FOR(i,a,b) for(register int i=(a);i<=(b);++i)
#define main Main();signed main(){ios::sync_with_stdio(0);cin.tie(0);return Main();}signed Main
using namespace std;
const int N=2e3+10;
int ID,n,Var,Dep;
int st[N],top,Lg[N];
bool vis[N];
double ans;
inline void dfs(int u,int var,int dep){
	if(u==n)return Var+=var,Dep+=dep,void();
	FOR(i,1,top)if(!vis[st[i]]){
		st[++top]=st[i]<<1,st[++top]=st[i]<<1|1,vis[st[i]]=1;
		dfs(u+1,var+Lg[st[i]]+2,max(dep,Lg[st[i]]+1));
		top-=2,vis[st[i]]=0;
	}
}
signed main(){
	cin>>ID>>n;
	FOR(i,2,N-5)Lg[i]=Lg[i>>1]+1;
	st[top=1]=1,dfs(1,0,0);
	ans=(ID==1)?1.0*Var/n:1.0*Dep;
	FOR(i,1,n-1)ans/=i;
	cout<<fixed<<setprecision(6)<<ans<<endl;
	return 0;
}
100pts
Question 1
每次分裂时,总价值增加(原叶子的深度+2)。
那么设 \(f_i\) 表示该树有 \(i\) 个叶子节点时,叶子节点平均深度的平均值(期望)。
那么 \(f_i \cdot i = f_{i-1}\cdot(i-1)+(f_{i-1}+2)\)。
我们分解上式,进行解释:
- \(f_i \cdot i\) 表示该树有 \(i\) 个叶子节点时,叶子节点总深度的平均值(期望);
 - \(f_{i-1} \cdot (i-1)\) 表示该树有 \(i-1\) 个叶子节点时,叶子节点总深度的平均值(期望);
 - \(f_{i-1}+2\) 表示(叶子节点平均深度的平均值的深度+2)。
 
其实就是"每次分裂时,总价值增加(原叶子的深度+2)"的意思。
D ans;
namespace Sub1{
	signed Cmain(){
		FOR(i,2,n)ans+=2.0/i;
		cout<<fixed<<setprecision(6)<<ans<<endl;
		return 0;
	}
}
Question 2
法 1
朴素方法
设 \(f_{i,j}\) 表示一棵 \(i\) 个叶子节点的树,深度为 \(j\) 的概率,故
\[f_{i,j} = \frac{\sum f_{k,q} \cdot f_{i-k,j-1}}{n-1}
\]
记得去掉重复的,即两边都是 \(j-1\) 个节点的情况。
时间复杂度 \(O(n^4)\)。
namespace Sub2{
	D f[N][N];
	signed Cmain(){
		f[1][0]=1.0;
		FOR(i,2,n)FOR(j,1,i){
			FOR(k,1,i-1){
				FOR(q,0,j-2)f[i][j]+=f[k][q]*f[i-k][j-1]*2;
				f[i][j]+=f[k][j-1]*f[i-k][j-1];
			}
			f[i][j]/=i-1;
		}
		FOR(i,1,n)ans+=f[n][i]*i;
		cout<<fixed<<setprecision(6)<<ans<<endl;
		return 0;
	}
}
优化方法
我们加入前缀和优化,时间复杂度 \(O(n^3)\)。
namespace Sub2{
	D f[N][N],p[N][N];
	signed Cmain(){
		p[1][0]=f[1][0]=1.0;
		FOR(i,1,n)p[1][i]=p[1][i-1]+f[1][i];
		FOR(i,2,n){
			FOR(j,1,i){
				FOR(k,1,i-1){
					if(j>1)f[i][j]+=p[k][j-2]*f[i-k][j-1]*2;
					f[i][j]+=f[k][j-1]*f[i-k][j-1];
				}
				f[i][j]/=(i-1);
			}
			p[i][0]=f[i][0];
			FOR(j,1,n)p[i][j]=p[i][j-1]+f[i][j];
		}
		FOR(i,1,n)ans+=f[n][i]*i;
		cout<<fixed<<setprecision(6)<<ans<<endl;
		return 0;
	}
}
法 2
对于上面的统计答案,我们发现:
\[E = \sum_{i=1}^n i \cdot P_{Dep=i}
\]
那么我们通过一些 我不太懂的 统计学知识,转化为:
\[E = \sum_{i=1}^n P_{Dep \ge i}
\]
于是我们把状态更改为:设 \(f_{i,j}\) 表示一棵 \(i\) 个叶子节点的树,深度大于等于 \(j\) 的概率,故
\[ f_{i,j} = \frac{\sum_{k=1}^{i-1} f_{k,j-1} + f_{i-k,j-1} - f_{k,j-1} \cdot f_{i-k,j-1}}{i-1}
\]
namespace Sub2{
	D f[N][N];
	signed Cmain(){
		FOR(i,1,n)f[i][0]=1.0;
		FOR(i,2,n)FOR(j,1,i-1){
			FOR(k,1,i-1)f[i][j]+=f[k][j-1]+f[i-k][j-1]-f[k][j-1]*f[i-k][j-1];
			f[i][j]/=i-1;
		}
		FOR(i,1,n)ans+=f[n][i];
		cout<<fixed<<setprecision(6)<<ans<<endl;
		return 0;
	}
}
时间复杂度 \(O(n^3)\)。
后记
正解中,每个代码都有乘上一个 \(\frac{1}{i-1}\),在这里解释一下,因为对于一棵总节点数相同的树,在左右不管分配多少节点,总方案数都相同,乘上一个 \(\frac{1}{i-1}\) 是选到某种方案的概率。

                
            
        
浙公网安备 33010602011771号