AT_utpc2012_12
简单题,首先你要会一个 slope trick。
题意
给出一颗有根树,你可以花费 \(k\) 个代价将某个点的权值增大或减小 \(k\)。
现在想使这棵树的每个点都比子节点的点权大,请你求出最小的代价。
题解
先考虑一个简单点的问题:要求权值不小于儿子的权值。
设 \(f_{x,i}\) 为最终 \(x\) 这个点的权值为 \(i\),满足其子树的限制的最小代价。显然对于叶子节点,\(f_x=|a_x-i|\)。
转移显然,令 \(g_x\) 为 \(f_x\) 的前缀最小值,有转移:
\(f_{x_i}=|a_x-i|+\sum\limits_{v\in s_x}g_{x_i}\)。
套用 slope trick,用一个堆来维护这个 \(g\) 函数。
第一步将所有子树的函数求和,可以可并堆实现,直接把子树的堆合并上来就行,注意此时函数的最后一段斜率为 \(0\)。
第二步加上函数 \(|a_x-i|\),相当于添加两个决策点 \(a_x\),分类讨论他与现在函数最后一个拐点的位置关系,更新答案即可。
最后因为维护的是前缀 \(\min\) 的 \(g\) 函数,我们在最大斜率为 \(0\) 的函数上加上一个绝对值函数,此时函数的最大斜率是 \(1\),要把最后大于 \(1\) 的部分推平直接 \(\operatorname{pop}\) 就行。
所以我们就是维护一个子树的可并大根堆,每次由子树合并上来,插入两个 \(a_x\),若其小于堆的最大值那么就更新答案,最后删除堆顶元素。
最后就是要求他的权值必须大于其儿子权值,很经典的,若一个点的深度为 \(d_x\),把 \(a_x+d_x\) 当作他的权值,就可以当成是大于等于来做了。
代码很好写,但是不想写左偏树/cf。
#include<iostream>
#include<vector>
#include<bits/extc++.h>
using ll = long long; const int N = 1e6 + 10;
__gnu_pbds::priority_queue<ll> q[N];
ll n, d[N], v[N], rs; std::vector<int> g[N];
void dfs(int x, int fa){
d[x] = d[fa] + 1; q[x].push(v[x]+d[x]);
for(auto v:g[x]) dfs(v, x), q[x].join(q[v]);
if(v[x]+d[x]<q[x].top()){
rs += q[x].top()-v[x]-d[x];
q[x].pop(), q[x].push(v[x]+d[x]);
}
}
int main(){
std::ios::sync_with_stdio(false);
std::cin >> n >> v[1];
for(int i = 2, x; i <= n; i++)
std::cin >> x >> v[i], g[x].push_back(i);
dfs(1, 0); std::cout << rs;
}