[闵可夫斯基和] [slope trick] P3642 [APIO2016] 烟花表演
posted on 2025-05-08 12:18:49 | under | source
题意:给出 \(n\) 个点的有根树,边带权,可以花费 \(1\) 的代价使一条边 \(+1\) 或 \(-1\),求最小代价使得根到所有叶子的距离相等。\(n\le 3\times 10^5\)。
显然有 dp,设 \(f_{i,j}\) 为 \(i\) 子树内叶子距离均为 \(j\) 的代价,转移:
\[f_{u,i}=\sum\limits_{v\in son_u} \min\limits_{0\le j\le i}( f_{v,j}+|c_u-(i-j)|)
\]
考虑 slope trick,\(f_u\) 视为函数图象。不妨归纳地认为 \(f_u\) 是一个下凸函数。
转移可以分为两部分,第一部分是儿子向父亲的函数变换,也就是内层的 \(\min,+\) 卷积,那么就是函数 \(f_v\) 和绝对值函数 \(h_v(x)=|c_v-x|\) 的闵可夫斯基和,记为 \(g_v\)。第二部分就是大力相加 \(g_v\)。
大致思路有了,想想具体实现。我们考虑维护函数的拐点,从左往右每个拐点斜率 \(+1\)(此题显然没有浮点数),拐点可以相同以表示斜率不止 \(+1\)。那么对于第二部分,直接合并 \(g_v\) 的拐点即可。
对于第一部分,注意 \(h_v\) 函数定义域在自然数上,左半边有限为 \(c_v\),而右半边是无限长。所以我们找到 \(f_v\) 斜率为 \(0\) 的部分 \([L,R]\)。那么变换到 \(g_v\),就在 \(L\) 左边接一个 \(c_v\) 长的斜率为 \(-1\) 的线段,\(R\) 右边的部分推平为斜率为 \(1\)。
上述过程可以这样实现:
- 删去 \(f_v\) 前 \(|son_v|-1\) 大的拐点,此时右边两个拐点即为 \(L,R\)。这是因为 \(f_v\) 的 \(|son_v|\) 个儿子每个都会增加一条斜率 \(1\) 的射线。
- 让 \(L,R\) 均增大 \(c_v\)。
于是用可并堆维护即可,\(O(n\log n)\)。
- 注意:可以认为 \(f_u\) 存在一个为正无穷的拐点,但是不必计入列表中。初始化叶子节点列表为空,相当于认为 \(f_{u,i}=i\),实际是没有问题的,可以视为延长父边。
代码
灰常好写,细节也没有。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5;
int n, m, p[N], c[N], cs, sn[N], ans;
namespace ZPS{
struct node{int ls, rs, d, x;} t[N << 1];
int tot, rot[N];
inline int newnode(int x) {++tot; t[tot] = {0, 0, 0, x}; return tot;}
inline void psup(int u){
if(t[t[u].ls].d < t[t[u].rs].d) swap(t[u].ls, t[u].rs);
t[u].d = t[t[u].rs].d + 1;
}
inline int mer(int x, int y){
if(!x || !y) return x + y;
if(t[x].x < t[y].x) swap(x, y);
t[x].rs = mer(t[x].rs, y);
psup(x);
return x;
}
inline int del(int x) {return mer(t[x].ls, t[x].rs);}
}using namespace ZPS;
signed main(){
cin >> n >> m;
for(int i = 2; i <= n + m; ++i) scanf("%lld%lld", &p[i], &c[i]), ++sn[p[i]], ans += c[i];
for(int i = n + m; i > 1; --i){
int L = 0, R = 0;
if(i <= n){
while(--sn[i]) rot[i] = del(rot[i]);
R = t[rot[i]].x, rot[i] = del(rot[i]);
L = t[rot[i]].x, rot[i] = del(rot[i]);
}
rot[i] = mer(rot[i], newnode(L + c[i]));
rot[i] = mer(rot[i], newnode(R + c[i]));
rot[p[i]] = mer(rot[p[i]], rot[i]);
}
while(sn[1]--) rot[1] = del(rot[1]);
while(rot[1]) ans -= t[rot[1]].x, rot[1] = del(rot[1]);
cout << ans;
return 0;
}

浙公网安备 33010602011771号