树上最小权覆盖(可并堆)

题目大意

  以(1)为根,(n)个点的树,给(m)条直上直下的链,每个链有权值,求最小权覆盖。

解题思路

  曾经猫老师讲过的一道题。首先考虑序列上的最小权区间覆盖,一个人尽皆知的方法是数据结构优化(dp)做,但还有一种方法是用堆。堆里存二元组((a,b))表示花费(a)使得([1,b])被覆盖,这个以(a)为关键字,然后按照左端点排序,每次取出(b)([l,r])内的最小值,然后更新答案。尝试把这个做法扩展到树上,发现需要进行合并,那么就可以可并堆了,时间复杂度(O(nlogn))

代码

#pragma GCC optimize("inline","Ofast")
#pragma GCC optimize(3)
#include<bits/stdc++.h>

using namespace std;
const int N=1000005;

inline int rd(){
	int x=0,f=1; char ch=getchar();
	while(!isdigit(ch)) f=ch=='-'?0:1,ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	return f?x:-x;
}

int n,m,fa[N],dep[N],rt[N],dis[N],ch[N][2],top[N],val[N],tot,tag[N];
vector<pair<int,int> > v[N];
vector<int> G[N];

void pushdown(int x){
	if(tag[x]){
		val[x]+=tag[x];
		tag[ch[x][0]]+=tag[x];
		tag[ch[x][1]]+=tag[x];
		tag[x]=0;
	}
}

int merge(int x,int y){
	if(!x || !y) return (x|y);
	pushdown(x); pushdown(y);
	if(val[x]>val[y]) swap(x,y);
	ch[x][1]=merge(ch[x][1],y);
	if(dis[ch[x][0]]<dis[ch[x][1]]) swap(ch[x][0],ch[x][1]);
	dis[x]=dis[ch[x][1]]+1; 
	return x;
}

void DFS(int x){
	for(int i=0;i<v[x].size();i++){
		val[++tot]=(v[x][i]).first;
		top[tot]=(v[x][i]).second;
		rt[x]=merge(rt[x],tot);
	}
	int sum=0,w;
	for(int i=0;i<G[x].size();i++){
		DFS(G[x][i]); pushdown(rt[G[x][i]]); 
		w=val[rt[G[x][i]]]; tag[rt[x]]+=w; 
		tag[rt[G[x][i]]]+=sum; sum+=w;
		rt[x]=merge(rt[x],rt[G[x][i]]);
	}
	while(rt[x]){
		pushdown(rt[x]);
		if(dep[top[rt[x]]]>dep[x]) rt[x]=merge(ch[rt[x]][0],ch[rt[x]][1]);
		else break;
	}
	if(!rt[x]) {puts("-1"); exit(0);}
}

int main(){
	dep[1]=1;
	n=rd(),m=rd(); int x,y,z;
	for(int i=2;i<=n;i++){
		fa[i]=rd(); dep[i]=dep[fa[i]]+1;
		G[fa[i]].push_back(i);
	}
	for(int i=1;i<=m;i++){
		x=rd(),y=rd(),z=rd();
		v[y].push_back(make_pair(z,x));
	}
	DFS(1); printf("%d
",val[rt[1]]);
	return 0;
}
原文地址:https://www.cnblogs.com/sdfzsyq/p/10561176.html