题目:
给定一棵
n
n
n个结点的树,除了结点1外的每一个结点上有一个怪物,到达一个结点后一定要打这个结点上的怪物,打第
i
i
i个结点上的怪物后会扣
a
i
a_i
ai滴血,打完后会加
b
i
b_i
bi滴血,每个结点上的怪物打过后再到达这个结点就不用打怪了,从结点1出发,血量小于0时游戏结束,问最少的初始血量,可以打完所有的怪。
(
2
≤
n
≤
1
0
5
,
0
≤
a
i
,
b
i
≤
1
0
9
)
(2 \le n \le 10^5,0 \le a_i,b_i \le 10^9)
(2≤n≤105,0≤ai,bi≤109)
题解:
可以发现一个结点的怪只有在将其父结点上的怪打掉后才有机会打,将无根树转化为以1为根的有根树,那么我们就是要在满足树的拓扑序的限制下安排打怪的最优顺序。
先考虑没有树的拓扑序限制的情况,那么可以将怪分为两种情况,一种
a
i
<
b
i
a_i<b_i
ai<bi,一种
a
i
>
b
i
a_i>b_i
ai>bi,显然第一种打完是回血的,第二种打完是扣血的,那么肯定第一种排在第二种前面。对于第一种内部的顺序,考虑两个相邻的结点
i
,
j
i,j
i,j,在这之前还剩的血量为
v
a
l
val
val,那么如果
i
i
i排在
j
j
j前面,要满足
min
{
v
a
l
−
a
i
,
v
a
l
−
a
i
+
b
i
−
a
j
}
<
min
{
v
a
l
−
a
j
,
v
a
l
−
a
j
+
b
j
−
a
i
}
\min\{val-a_i,val-a_i+b_i-a_j\}<\min\{val-a_j,val-a_j+b_j-a_i\}
min{val−ai,val−ai+bi−aj}<min{val−aj,val−aj+bj−ai},将
v
a
l
val
val提出来并消掉,式子变成
min
{
−
a
i
,
−
a
i
+
b
i
−
a
j
}
>
min
{
−
a
j
,
−
a
j
+
b
j
−
a
i
}
\min\{-a_i,-a_i+b_i-a_j\}>\min\{-a_j,-a_j+b_j-a_i\}
min{−ai,−ai+bi−aj}>min{−aj,−aj+bj−ai},由于
a
i
<
b
i
,
a
j
<
b
j
a_i<b_i,a_j<b_j
ai<bi,aj<bj,所以
−
a
i
+
b
i
−
a
j
>
−
a
j
,
−
a
j
+
b
j
−
a
i
>
−
a
i
-a_i+b_i-a_j>-a_j,-a_j+b_j-a_i>-a_i
−ai+bi−aj>−aj,−aj+bj−ai>−ai,那么如果
−
a
i
>
−
a
j
-a_i>-a_j
−ai>−aj,即
a
i
<
a
j
a_i<a_j
ai<aj,那么左式大于右式,如果
a
i
>
a
j
a_i>a_j
ai>aj,即
−
a
i
<
−
a
j
-a_i<-a_j
−ai<−aj,那么右式大于左式,所以左式大于右式等价于
a
i
<
a
j
a_i<a_j
ai<aj。同理,第二种内部的先后顺序,如果
i
i
i排在
j
j
j前面,那么要有
b
i
>
b
j
b_i>b_j
bi>bj。
现在考虑加进树的拓扑序限制,如果按照前面的规则排序,可以得到序列
p
1...
n
−
1
p_{1...n-1}
p1...n−1,其中结点1已经不算在内了,如果
p
1
p_1
p1的父结点为
1
1
1,那么
p
1
p_1
p1就可以确定为下一个点,不然,我们可以知道如果
f
a
p
1
fa_{p_1}
fap1被确定了,那么下一个肯定是
p
1
p_1
p1,所以我们可以把
p
1
p_1
p1合并到
f
a
p
1
fa_{p_1}
fap1,具体的
f
a
p
1
fa_{p_1}
fap1的属性
a
a
a变成合并后的最小值,属性
b
b
b变成最小值后面加上去的值,因为这样合并的信息依然满足之前分析的排序规则,这样合并后相当于
f
a
p
1
fa_{p_1}
fap1被确定后,
p
1
p1
p1也就同时被确定了,然后将
p
1
p_1
p1的所有子结点的父结点变成
f
a
p
1
fa_{p_1}
fap1。合并完以后问题规模就从
n
−
1
n-1
n−1变成了
n
−
2
n-2
n−2,每次都这样处理即可得到答案,其中维护序列可以用
s
e
t
set
set,合并可以用并查集。其中在写
s
e
t
set
set的
c
m
p
cmp
cmp的时候一定不要出现大于小于都返回
f
a
l
s
e
false
false的情况,因为这样
s
e
t
set
set会认为这两个值是一样的,会同时被删掉,可以用一个唯一标识
i
d
id
id来解决这个问题。
复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;
#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
const int mod=1e9+7;
const int INF=0x3f3f3f3f;
const int maxn=2e5+5;
ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int T,n,cnt,rnd;
vector<int>g[maxn];
int fa[maxn],del[maxn],pre[maxn];
ll cur,mn;
struct node{
ll a,b;
int ii;
node(){}
node(ll a,ll b):a(a),b(b){}
bool operator<(node &ot)const{
int id=a<b,otid=ot.a<ot.b;
if(id==otid){
if(id==1){
if(a==ot.a){
return ii<ot.ii;
}
return a<ot.a;
}
else{
if(b==ot.b){
return ii<ot.ii;
}
return b>ot.b;
}
}
return id>otid;
}
}buf[maxn];
struct cmp{
bool operator()(int a,int b){
return buf[a]<buf[b];
}
};
set<int,cmp>se;
void dfs(int u,int f){
fa[u]=f;
for(auto v:g[u]){
if(v==f)continue;
dfs(v,u);
}
}
void init(){
for(int i=1;i<=n;i++){
pre[i]=i;
}
cnt=0;
}
int find(int u){
if(pre[u]==u)return u;
return pre[u]=find(pre[u]);
}
void merge(){
int u=*se.begin();
se.erase(se.begin());
int v=find(fa[u]);
if(del[v]){
del[u]=1;
cur-=buf[u].a;
mn=min(mn,cur);
cur+=buf[u].b;
}
else{
se.erase(v);
ll aa,bb;
aa=min(-buf[v].a,-buf[v].a+buf[v].b-buf[u].a);
bb=-buf[v].a+buf[v].b-buf[u].a+buf[u].b-aa;
buf[v].a=-aa;buf[v].b=bb;
se.insert(v);
pre[u]=v;
}
}
int main(void){
// freopen("in.txt","r",stdin);
scanf("%d",&T);
rnd=0;
while(T--){
++rnd;
scanf("%d",&n);
for(int i=1;i<=n;i++)g[i].clear();
int u,v;
for(int i=2;i<=n;i++){
scanf("%lld%lld",&buf[i].a,&buf[i].b);
buf[i].ii=i;
}
for(int i=1;i<=n-1;i++){
scanf("%d%d",&u,&v);
g[u].pb(v);
g[v].pb(u);
}
se.clear();
init();
dfs(1,0);
for(int i=2;i<=n;i++){
del[i]=0;
se.insert(i);
}
buf[1].a=buf[1].b=0;
del[1]=1;
cur=0;
mn=1e18;
while(!se.empty()){
merge();
}
printf("%lld\n",-mn);
}
return 0;
}