AGC023F 01 on Tree

AGC023F

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

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

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

n ≤ 2 ∗ 1 0 5 n\le 2*10^5 n2105


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

正解似曾相识?

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

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

考虑两个序列的合并:记第一个序列有 a 1 a_1 a1 0 0 0,有 b 1 b_1 b1 1 1 1;第二个同理。第一个放在第二个前,贡献为 b 1 a 2 b_1a_2 b1a2;反之贡献为 b 2 a 1 b_2a_1 b2a1

假如第一个放第二个前更优,那么 b 1 a 2 < b 2 a 1 b_1a_2<b_2a_1 b1a2<b2a1,也就是 a 2 b 2 < a 1 b 1 \frac{a_2}{b_2}<\frac{a_1}{b_1} b2a2<b1a1

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

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


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;
}

猜你喜欢

转载自blog.csdn.net/A1847225889/article/details/108742913