版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ccsu_cat/article/details/81325469
多校三第H题,题意有n个节点构成一棵树,除了节点1每个节点有个怪兽和两个属性a,b,表示杀死这个怪兽自己要先扣血a,扣完血后自己的血不能是负数,再加血b,从1节点出发,不能跨越节点,求至少需要多少血量才能杀死所有怪兽。
思路:假设可以跨越节点杀怪兽,当a<b时,杀完怪兽后自己反而加血,因此当a<b时优先杀a越小的怪兽,当a>=b时,优先杀b越大的,这个排序方法就是流水线排序,和多校1第二题排序法一样。现在加上限制条件,不能跨越节点杀怪兽,先将所有节点排好序p1 p2 p3 ...pn,先出p1,从一号节点出发,如果p1可以直接到达,那么就先杀p1,如果p1不能直接到达,就找到p1的父亲,如果p1的父亲被杀了,下一个肯定杀p1,怎么搞定这个操作呢,只要将p1与其父亲这两个节点合并就好了,就表示杀其父亲也顺便把p1杀了,因此所有操作就是个不停的合并节点的过程,用优先队列实现其过程就好了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long LL;
const LL inf=1e14;
const int maxn=2e5+10;
struct node
{
LL a,b;
int u,num;
bool operator<(const node& t)const//流水线排序
{
if(a<b)
{
if(t.a<t.b)
return a>t.a;
return false;
}
else
{
if(t.a>=t.b)
return b<t.b;
return true;
}
}
void operator+=(const node& t)//节点合并,节点的a保存的是至少需要a的血量才能杀死这个节点的怪兽
{
LL na=max(a,a-b+t.a),nb=b+t.b-t.a-a+na;
a=na,b=nb;
}
}a[maxn];
int vis[maxn],fa[maxn],del[maxn];
vector<int>G[maxn];
priority_queue<node>q;
void init(int n)
{
for(int i=0;i<=n;i++)
vis[i]=del[i]=0,G[i].clear();
}
void dfs(int u,int father)
{
fa[u]=father;
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i];
if(v!=father)
dfs(v,u);
}
}
int F(int u)
{
if(del[fa[u]])//父亲也被删除,就找爷爷
return fa[u]=F(fa[u]);//并查集路径压缩
return fa[u];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,u,v,pos=0;
scanf("%d",&n);
init(n);
for(int i=2;i<=n;i++)
{
scanf("%lld%lld",&a[i].a,&a[i].b);
a[i].u=i,a[i].num=0;
}
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
a[1].a=a[1].b=a[1].num=0,a[1].u=1;
for(int i=2;i<=n;i++)
q.push(a[i]);
while(!q.empty())
{
node t=q.top();q.pop();
if(del[t.u])continue; //已经被删除
if(t.num!=vis[t.u])continue; //这个节点已经被更新
del[t.u]=1; //删除这个节点
int f=F(t.u); //找到其父亲
a[f]+=a[t.u];
if(f>1)
{
a[f].num=vis[f]=++pos;//更新 f 节点
q.push(a[f]);
}
}
printf("%lld\n",a[1].a);
}
}