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

题目大意

  以\(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\n",val[rt[1]]);
	return 0;
}
posted @ 2019-03-19 20:41  Monster_Qi  阅读(430)  评论(0编辑  收藏  举报