AGC023F 01 on Tree

AGC023F

一棵树,每个点上有\(0\)\(1\)的点权。

你要钦定一个遍历顺序,使得:每个点遍历之前,它的所有祖先都被遍历过。

按照遍历顺序得到一个点权序列,求这个点权序列的逆序对的最小值。

\(n\le 2*10^5\)


一天连续看两道题的题解有点心虚。。。

正解似曾相识?

关键思路:将“价值”最大的节点和父亲合并。

每个节点代表一个遍历的序列,每次选择一个点和父亲合并(意味着选了父亲之后立刻选它),一直这样操作直到剩下一个点。可以发现这样操作的方案可以对应上所有的遍历方案。

考虑两个序列的合并:记第一个序列有\(a_1\)\(0\),有\(b_1\)\(1\);第二个同理。第一个放在第二个前,贡献为\(b_1a_2\);反之贡献为\(b_2a_1\)

假如第一个放第二个前更优,那么\(b_1a_2<b_2a_1\),也就是\(\frac{a_2}{b_2}<\frac{a_1}{b_1}\)

根据\(\frac{a_i}{b_i}\)排序。于是就可以决定局部(菊花图)的先后顺序。

由于合并之后,父亲的\(a\)\(b\)会变化,所以要扩展一下:找到整张图的\(\frac{a_i}{b_i}\)最大的点,将它和父亲合并。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 200010
#define ll long long
int n;
int fa[N],a[N],b[N];
int dsu[N];
int getdsu(int x){return dsu[x]==x?x:dsu[x]=getdsu(dsu[x]);}
struct Info{
	int a,b,x;
} h[N*2];
bool cmph(Info son,Info fa){return (ll)fa.b*son.a<(ll)son.b*fa.a;}
int nh;
int main(){
	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for (int i=2;i<=n;++i)
		scanf("%d",&fa[i]);
	for (int i=1;i<=n;++i){
		int x;
		scanf("%d",&x);
		(x==0?a[i]:b[i])=1;
	}
	for (int i=1;i<=n;++i)
		h[nh++]={a[i],b[i],i};
	make_heap(h,h+nh,cmph);
	for (int i=1;i<=n;++i)
		dsu[i]=i;
	ll ans=0;
	while (nh){
		int x=h[0].x;
		pop_heap(h,h+nh--,cmph);
		if (fa[x] && dsu[x]==x){
			int y=getdsu(fa[x]);
			dsu[x]=y;
			ans+=(ll)a[x]*b[y];
			a[y]+=a[x],b[y]+=b[x];
			h[nh++]={a[y],b[y],y};
			push_heap(h,h+nh,cmph);
		}
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-09-22 22:37  jz_597  阅读(85)  评论(0编辑  收藏  举报