Address
洛谷 P3642
BZOJ 4585
UOJ #205
LOJ #2568
Solution
- NOIP 2018 之后 A 掉的第一道题,祭
- 看上去是水题,但是发现边权可以减一之后就不是那么容易了
- 很容易想到状态: 表示把 到子树内所有叶子的距离都调成 的最小代价(下面 为边 的长度)
可以大胆猜想是关于 的下凸函数- 假设 只有 一个子节点
- 设 时 取得最小值
- 然后分类讨论一下转移:
- (1)
- 这时候 ,最优的方案是把 的边权改成
- (2)
- 这时候最优的方案是把 的边权改成
- (3)
- 这时候最优的方案是不修改 的边权
- (4)
- 这时候最优的方案是把 的边权改成
- 把 和 看作分段函数,可以发现每个段都是一次的
- 上面四个转移相当于
- (1)把横坐标 内的直线上移 个单位
- (2)把横坐标 内的直线右移 个单位
- (3)在横坐标 内加入一条斜率为 的直线
- (4)在横坐标 内加入一条斜率为 的直线
- 我们考虑维护分段函数的拐点
- 注意到每经过一个拐点,函数的斜率加一
- 对于叶子节点,有两个拐点
- 这是一棵树,处理点 时如何将 转移到 父亲 呢
- 先说:最右端直线的斜率是 u 的子节点数(接下来会证明)
- 用一个大根堆维护拐点横坐标
- 先不断弹出最右端斜率大于 的直线,使得最后一段直线的斜率为
- 然后把斜率为 的直线段对应的两个拐点右移 个单位
- 把这个大根堆合并到父亲
- 需要用到可并堆(左偏树)
- 而这时候每个点往父亲合并时最右端的斜率一定为 ,故 最右端直线的斜率是 的子节点数
- 利用这个就能判断最右端直线的斜率
- 先暂时设 为所有边权和
- 把 的折线的右端斜率大于 的直线全部弹掉
- 注意到所有边权和就是 在 点的值
- 于是只需要从横坐标 开始,通过拐点和斜率计算函数减少的量即可
- 可以得出把 减去这时候左偏树内所有点的横坐标之和就是答案
- 复杂度
Code
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
inline int read()
{
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
template <class T>
inline void Swap(T &a, T &b) {a ^= b; b ^= a; a ^= b;}
typedef long long ll;
const int N = 6e5 + 5, M = N << 1;
int n, m, fa[N], len[N], rt[N], cnt[N], ToT;
ll ans;
struct node
{
int lc, rc, dis; ll val;
} T[M];
int mer(int x, int y)
{
if (!x || !y) return x + y;
if (T[x].val < T[y].val) Swap(x, y);
T[x].rc = mer(T[x].rc, y);
T[x].dis = T[x].rc ? T[T[x].rc].dis + 1 : 0;
if (!T[x].lc || T[T[x].lc].dis < T[T[x].rc].dis)
Swap(T[x].lc, T[x].rc);
return x;
}
void pop(int &x)
{
x = mer(T[x].lc, T[x].rc);
}
int main()
{
int i;
n = read(); m = read();
For (i, 2, n + m)
cnt[fa[i] = read()]++, ans += (len[i] = read());
Rof (i, n + m, 2)
{
ll l = 0, r = 0;
cnt[i]--;
while (cnt[i] > 0) pop(rt[i]), cnt[i]--;
if (i <= n) r = T[rt[i]].val, pop(rt[i]),
l = T[rt[i]].val, pop(rt[i]);
int x, y;
T[x = ++ToT] = (node) {0, 0, 0, l + len[i]};
T[y = ++ToT] = (node) {0, 0, 0, r + len[i]};
rt[i] = mer(rt[i], mer(x, y));
rt[fa[i]] = mer(rt[fa[i]], rt[i]);
}
while (cnt[1]--) pop(rt[1]);
while (rt[1]) ans -= T[rt[1]].val, pop(rt[1]);
std::cout << ans << std::endl;
return 0;
}