技能树
题目描述
“OI 的学习,理应循序渐进”。
OI 中技能的加点形如一棵以 $1$ 为根的树形结构,每个点的父亲就是它的前置知识,比如“min25 筛”应该在“莫比乌斯反演”之后学习,“主席树”应该在“树状数组”之后学习,形式化的来说,对于树上每个节点 $u$ ,必须先学前置知识,也就是它在技能树上父亲 $fa(u)$ 。
学习知识的过程可能是痛苦的,也可能是愉悦的,学习 $i$ 号知识需要消耗 $a_i$ 点愉悦度,学完之后因为满满的成就感会增加 $b_i$ 点愉悦度,注意,此处先结算 $a_i$ 后结算 $b_i$ ,二者并非同一时刻结算。
现在,我们已经学习完了 $1$ 号知识点,并拥有了一个未知的初始愉悦度,因为我们不希望学习过程中出现过分痛苦的情况,所以我们想知道:至少需要多少点初始愉悦度,使得能通过合理的学习顺序学习完技能树上所有知识,并且学习过程中任意时刻愉悦度都不为负?
数据范围
$n \le 10^5,0 \le a_i,b_i \le 10^9$
题解
可以先考虑序列上如何操作。
首先我们应该选择那些 $a_i \le b_i$ 的点,对于这些点我们应先选择 $a_i$ 更小的。
对于那些 $a_i > b_i$ 的点,可以假定 $i$ 比 $j$ 更优,然后列出不等式,发现满足这个的条件是 $b_i>b_j$ ,所以从 $b_i$ 大的开始选即可。
考虑在一棵树上,因为有父亲的限制,所以不能像上述贪心,我们发现如果有一个优先级最高的点,如果它的父亲被选了,那下一个一定会选它,所以维护优先队列,每次选择优先级最高的,跟当前父亲合并即可,这个可以用并查集实现。
代码
#include <bits/stdc++.h> #define LL long long using namespace std; const int N=2e5+5; LL a[N],b[N]; int n,fa[N],f[N],hd[N],V[N],nx[N],t; struct O{ int x; friend bool operator < (const O& A,const O& B){ if (b[A.x]>=a[A.x] && b[B.x]>=a[B.x]) return a[A.x]>a[B.x]; if (b[A.x]<a[A.x] && b[B.x]<a[B.x]) return b[A.x]<b[B.x]; return b[A.x]<a[A.x]; } }; priority_queue<O>q; int get(int x){ return f[x]==x?x:f[x]=get(f[x]); } void add(int u,int v){ nx[++t]=hd[u];V[hd[u]=t]=v; } void dfs(int x,int fr){ fa[f[x]=x]=fr; for (int i=hd[x];i;i=nx[i]) if (V[i]!=fr) dfs(V[i],x); } int main(){ cin>>n; for (int i=2;i<=n;i++) scanf("%lld%lld",&a[i],&b[i]), q.push((O){i}); for (int x,y,i=1;i<n;i++) scanf("%d%d",&x,&y), add(x,y),add(y,x); dfs(1,0);int u,v;LL A,B; while(!q.empty()){ u=q.top().x;q.pop(); v=get(fa[u]);f[u]=v; A=max(a[v],a[v]-b[v]+a[u]); B=b[v]+b[u]-a[v]-a[u]+A; a[v]=A;b[v]=B; } printf("%lld\n",a[1]);return 0; }