[题解] CF1540B Tree Array

[题解] CF1540B Tree Array

期望题,思维题,dp(递推)题,暴力题

传送门

题意

对一棵 \(n\) 个点无根树进行染色操作,染色规则如下:

开始时,等概率地 随机找到一个点将其染色;

然后 等概率地至少一条边连接已染色结点 的未染色结点进行染色。

最终会形成一个染色序列 \(a\),求 \(a\) 中逆序数对数的期望值。

最后对 \(1e9+7\) 进行取模。

\(n\leq 200\)

题解

观察题目不难发现,染色过程类似于 \(bfs\) 推进的过程,只不过每次只推进一个结点而已。

期望的线性性质

引用内容来自 刘汝佳《算法竞赛入门指南》。

有限个随机变量之和的数学期望等于每个随机变量的数学期望之和。例如,对于两个随机变量 \(X\) 和 \(Y\),\(E(X+Y)=E(X)+E(Y)\)。

全期望公式

类似全概率公式,把所有 不重复、不遗漏 地分成若干类,每类计算数学期望。然后把这些数学期望 按照每类的概率 加权求和。

其实一般期望题你已经会潜移默化地运用上述两个性质和公式,只不过你不知道而已。


因为这道题需要先选定根节点(首先染色的结点)然后扩展,不妨运用全期望公式:对于每一个根取一个期望,然后加起来除以 \(n\),比较简单的道理。

考虑选根之后,一个无序点对 \((a,b)\) 怎么会对答案产生贡献:\(a>b\) 且 \(a\) 先被扩展到。

令 \(LCA=getlca(a,b)\) ,我们只需要考虑 \(LCA\) 到 \(a\) 或 \(b\) 这一段即可,因为 \(rt\) 到 \(LCA\) 的相对概率为 \(1\),可以看作前提条件。

如果不清楚,可以看一看官方题解的这段话:

Note that, until reaching \(LCA\) , every possible process still has the same probability of reaching \(b\) before \(a\) as it did when the first node was chosen.

注意,在到达 \(LCA\) 之前,每个可能的过程在到达 \(a\) 之前到达 \(b\) 的概率仍然与第一个节点被选中时相同。

因此只需要考虑 \(LCA\) 以下的这部分,由于 \(n\leq 200\),考虑 \(\text O(n^2)\) 枚举点对,现在只需要求出 \(LCA\) 先到达 \(a\) 的概率,这也是本题唯一的思维难度所在。

考虑用递推搞定这个概率。

设 \(f[x,y]\) 为先到达 \(x\) 的概率,那么 \(f[x,y]=\frac{f[x-1,y]+f[x,y-1]}{2}\)。

直观理解一下,他的意义就是 往两个方向中的一个方向走的概率相同,均为 \(\frac{1}{2}\)

官方题解说的就很形象:

给你两个栈,每次有 \(p\) 的概率把第一个的栈顶弹出,\(p\) 的概率把第二个的栈顶弹出,\(1−2p\) 的概率啥也不做,问 \(a\) 比 \(b\) 先弹完的概率是多少。

我们并不关心这个概率是多少,我们只知道它往 \(a\) 和往 \(b\) 走的概率是相同的。因为我们分开讨论(枚举)了所有点对。

换句话说,如果真的用 \(1-2p\) 的概率往别处走了,那么到达 \(a\) 和到达 \(b\) 的概率为 \(0\),超出了基本范围。

总结

这道题蓝题难度(官方难度 2300),但是我感觉把他设成紫题难度也不过分,主要是最后概率那有点难想。

还是说 挖掘题目性质,进行等价转换,找对基本思路,用对基本算法

其中第一条是关键,类似于语文里的扣文本?

扣题才是关键,在大量思考之前永远不要想什么歪的东西,才能避免想当然的情况。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
#define int long long
const int maxn = 200 + 7 , maxlog = 19;
const int P = 1e9 + 7 , inv2 = 5e8 + 4;
int fa[maxn][maxlog],f[maxn][maxn],de[maxn];
int head[maxn],cnt=0;
struct edge{
	int to,nxt;
}e[maxn<<1];
inline void link(int u,int v){
	e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
int n;
int power(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%P;
		a=a*a%P;
		b>>=1;
	}
	return res;
}
#define read() read<int>()
void dfs(int u,int Fa){
	fa[u][0]=Fa;de[u]=de[Fa]+1;
	for(int i=1;i<maxlog;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==Fa)continue;
		dfs(v,u);
	}
}
int getlca(int x,int y){
	if(de[x]<de[y])swap(x,y);
	for(int i=maxlog-1;i>=0;i--)
		if(de[fa[x][i]]>=de[y])x=fa[x][i];
	if(x==y)return x;
	for(int i=maxlog-1;i>=0;i--)
		if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
signed main(){
	n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read();link(u,v);link(v,u);
	}
	for(int i=1;i<=n;i++)f[0][i]=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)f[i][j]=(f[i-1][j]+f[i][j-1])*inv2%P;
	long long ans=0;
	for(int i=1;i<=n;i++){
		dfs(i,0);
		for(int j=1;j<=n;j++)
			for(int k=1;k<j;k++){
				int LCA=getlca(j,k);
				ans=(ans+(long long)f[de[j]-de[LCA]][de[k]-de[LCA]])%P;
			}
	}
	printf("%lld\n",1LL*ans*power(n,P-2)%P);
	return 0;
}
上一篇:kruskar重构树


下一篇:[LOJ#6021]. 「from CommonAnts」寻找 LCR