题目:给定一棵 n 个点的树,除 1 外每个点有一只怪兽,打败它需要先消耗 ai点 HP,再恢复 bi点 HP。求从 1 号点出发按照最优策略打败所有怪兽一开始所需的最少 HP。
思路:以 1 为根将树转化成有根树,那么每只怪兽要在父亲怪兽被击败后才能被击败。假如没有父亲的限制,会产生一个最优的攻击顺序:
第一步:将怪兽分成两类:a < b 的和 a ≥ b 的,前一类打完会加血,后一类打完会扣血,显然最优策略下应该先打第一类再打第二类。对于 a < b 的怪兽,显然最优策略下应该按照 a 从小到大打。
第二步:对于 a ≥ b 的怪兽,考虑两只怪兽 i,j,先打 i 再打 j 的过程中血量会减少到 HP + min(−ai,−ai+ bi− aj),因为 a ≥ b,所以这等于 HP−ai−aj+bi。同理先打 j 再打 i 的过程中血量会减少到 HP − ai− aj+ bj。可以发现按照任何顺序都只和 b 有关,最优策略下需要让血量尽可能多,因此要按照 b 从大到小打。
第三步:加上父亲的限制,根据之前的优先级将子节点与父节点合并,即打完父亲后立即打这个子节点是最优的。
具体实现起来就是用优先队列来贪心了,中间合并时用并查集。 最开始写完代码,怎么改都是很正确,然而一交就wa,就很烦,拿数据本地跑发现怎么就第一组数据正确???,后面的都是一个数!!!然后发现竟然是0号节点没有初始化(代码风格不同吧,最后我的代码用的有0这一节点,答案存在了0这个点上)。。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=200005;
struct node{
int id,ti;
ll a,b;
bool operator < (const node &o)const
{
int d1=o.a<o.b,d2=a<b;
if(d1!=d2) return d1>d2;//第一步分类
if(d1>0) return o.a<a;
return o.b>b;//第二步分类
}
};
priority_queue<node>q;
node a[maxn];
int n,m,t;
vector<int>g[maxn];
int fa[maxn],f[maxn],vis[maxn];
int init()
{
while(!q.empty())q.pop();
for(int i=0;i<=n;i++)
{
g[i].clear();
a[i].a=a[i].b=0;
a[i].id=a[i].ti=0;
fa[i]=0;
f[i]=i;
vis[i]=0;
}
}
void dfs(int u,int pre)
{
fa[u]=pre;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(v==pre) continue;
dfs(v,u);
}
}
int getfa(int x)
{
return f[x]==x?x:f[x]=getfa(f[x]);
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
init();
for(int i=2;i<=n;i++)
{
scanf("%lld%lld",&a[i].a,&a[i].b);
a[i].id=i;
a[i].ti=0;
q.push(a[i]);
}
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
int tim=0,x;
node t,tt;
while(!q.empty())
{
t=q.top();
q.pop();
x=t.id;
if(vis[x]!=t.ti||x==0)continue;
int y=getfa(fa[x]);
f[x]=y;
vis[y]=++tim;
a[y].a+=max(0ll,-a[y].b+a[x].a);//合并后新的a
a[y].b=a[x].b+max(0ll,a[y].b-a[x].a);//合并后新的b
tt.a=a[y].a;
tt.b=a[y].b;
tt.ti=vis[y];
tt.id=y;
q.push(tt);
}
printf("%lld\n",a[0].a);
}
return 0;
}
/*
1
4
2 6
5 4
6 2
1 4
4 2
4 3
1
4
2 0
5 0
6 2
1 4
4 2
4 3
*/