cogimyunの小窝

Loading...

Luogu P12479 [集训队互测 2024] 长野原龙势流星群 题解

被 hack 的缺陷做法

我们考虑任意一个节点 \(i\) 如果已经是当前平均数最大值,那么必然不存在一个节点能够使节点 \(i\) 的平均数更大。考虑到节点 \(i\) 的父节点 \(j\) ,此时节点 \(j\) 的平均值一定 \(\le\) 节点 \(i\) 的平均值,所以此时可以通过节点 \(i\) 的平均值更新节点 \(j\) 的平均值,于是我们可以考虑使用优先队列维护最大值。

然后考虑题目中要求求的值是:

每个点 \(u\) 找到一个以 \(u\) 为根的非空连通块并最大化这个连通块内所有点的点权的平均值

所以此时的节点 \(i\) 在更新了父节点 \(j\) 后,就无法直接更新其它节点了(因为节点 \(i\) 并不能更新它的子节点),而是通过更新后的父节点 \(j\) 来间接更新其它节点,于是我们考虑用并查集维护连通块,在每次节点 \(i\) 在更新了父节点 \(j\) 后就将节点 \(i\) 并入节点 \(j\) 所在的连通块。

但值得注意的是,节点 \(i\) 在完成了更新节点 \(j\) 后,其部分不优的值可能仍然在优先队列中,所以每次查询优先队列最大值后还要判断这个最大节点 \(i\) 是否是自己所处连通块的根,如果是,则可以更新父节点 \(j\),否则跳过这个值。但以上的做法仍存在缺陷,于是我们不妨观察一下 hack 数据。

优化缺陷部分

考虑到 Subtask # 5 中的 hack 数据:

输入
4
1 2 2
1 2 4 3
输出
2.5000000000
3.0000000000
4.0000000000
3.0000000000

如果一个节点的平均值是 \(\frac{q}{p}\) 另一个节点的平均值是 \(\frac{s}{t}\),我们会令其中更大的值先被用于更新,但如果有 \(\frac{q}{p} = \frac{s}{t}\),我们应该认为 \(p\)\(t\) 更大的一个更优,我们可以感性理解一下,当分母更大时,那么加上另外一个值时对于这个平均值的影响更小。

CODE

#include<bits/stdc++.h>
using namespace std;
int n,fa[200005],f[200005],w[200005];
struct node{
	long long x;
	int y,id;
}tr[200005];
bool operator <(node x,node y){
	return (__int128(x.x*y.y)==__int128(y.x*x.y))?x.y<y.y:(x.x*y.y)<__int128(y.x*x.y);
}
priority_queue<node> q;
int getf(int x){
	return (f[x]==x)?x:f[x]=getf(f[x]);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=2;i<=n;i++)
		cin>>fa[i];
	for(int i=1;i<=n;i++){
		cin>>w[i];
		tr[i]={w[i],1,i};
		q.push(tr[i]);
		f[i]=i;
	}
	while(!q.empty()){
		node t=q.top();
		q.pop();
		if(f[t.id]!=t.id)
			continue;
		int k=getf(fa[t.id]);
		tr[k].x+=t.x;
		tr[k].y+=t.y;
		f[t.id]=k;
		if(k)
			q.push(tr[k]);
	}
	for(int i=1;i<=n;i++)
		cout<<setprecision(12)<<(long double)(1.0*tr[i].x/tr[i].y)<<endl;//setprecision(12)是保留12位小数,减小相对误差
	return 0;
}

posted @ 2025-10-30 18:25  cogimyun  阅读(3)  评论(0)    收藏  举报