树上最小权覆盖(可并堆)
题目大意
以\(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;
}