题面
解法
- 先不考虑树的情况,考虑一条链怎么做。
- 显然可以写出dp: f [ i ] = m i n ( f [ j ] + ( d [ i ] − d [ j ] ) p [ i ] + q [ i ] ) f[i]=min(f[j]+(d[i]-d[j])p[i]+q[i]) f[i]=min(f[j]+(d[i]−d[j])p[i]+q[i])。然后把式子稍作展开,可以发现这显然是一个斜率优化的形式。决策的点坐标为 ( d [ j ] , f [ j ] ) (d[j],f[j]) (d[j],f[j]),斜率为 p [ i ] p[i] p[i]。因为斜率并不单调,所以我们需要用cdq分治维护一个下凸壳来实现这一过程。
- 然后考虑一棵树的情况怎么实现。
- 大概有两种方法:一种是维护一个可持久化的单调队列来实现动态维护下凸壳的过程。这个可以用可持久化线段树来实现,但是细节较多。
- 另一种考虑点分治。假设我们正在处理分治重心为 r t rt rt的子树 x x x,先处理出 r t rt rt子树外的点的 d p dp dp值,然后用cdq分治来处理 x x x到 r t rt rt的路径上的点对 r t rt rt子树内 d p dp dp值的贡献,最后分治下去即可,比较容易实现。
- 时间复杂度: O ( n log 2 n ) O(n\log^2n) O(nlog2n)
【注意事项】
- 在cdq分治的过程中,由于从分治重心 r t rt rt到整棵树的根 x x x的路径中深度逐渐递减,所以维护凸包的时候需要稍微注意一下。
代码
#include <bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x < y ? x : y;}
template <typename T> void read(T &x) {
x = 0; int f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
const int N = 200010;
int rt, cnt, tot, f[N], fa[N], st[N], vis[N], siz[N], head[N];
ll d[N], p[N], q[N], dp[N], tx[N], ty[N], lim[N];
struct Node {
int x; ll v;
bool operator < (const Node &a) const {return v > a.v;}
} a[N];
struct Edge {int next, num; ll v;} e[N * 3];
ld slope(int i, int j) {
ld x = ty[i] - ty[j], y = tx[i] - tx[j];
return x / y;
}
void add(int x, int y, ll v) {e[++cnt] = (Edge) {head[x], y, v}, head[x] = cnt;}
void getdep(int x) {
for (int p = head[x]; p; p = e[p].next) {
int k = e[p].num; ll v = e[p].v;
d[k] = d[x] + v, getdep(k);
}
}
void getr(int x, int s) {
siz[x] = 1, f[x] = 0;
for (int p = head[x]; p; p = e[p].next) {
int k = e[p].num; if (vis[k]) continue;
getr(k, s), siz[x] += siz[k];
chkmax(f[x], siz[k]);
}
chkmax(f[x], s - siz[x]);
if (f[x] < f[rt] && siz[x] > 1) rt = x;
}
void dfs(int x) {
a[++tot] = (Node) {x, d[x] - lim[x]};
for (int p = head[x]; p; p = e[p].next) if (!vis[e[p].num]) dfs(e[p].num);
}
ll query(int i, int top) {
if (!top) return 1ll << 60;
int l = 2, r = top, j = st[1];
while (l <= r) {
int mid = (l + r) >> 1;
if (slope(st[mid - 1], st[mid]) >= p[i]) j = st[mid], l = mid + 1;
else r = mid - 1;
}
return dp[j] + (d[i] - d[j]) * p[i] + q[i];
}
void solve(int x, int s) {
if (s == 1) return tx[x] = d[x], ty[x] = dp[x], void();
f[rt = 0] = s, getr(x, s); int t = rt;
for (int p = head[rt]; p; p = e[p].next) vis[e[p].num] = 1;
solve(x, s - siz[rt] + 1); tot = 0;
for (int p = head[t]; p; p = e[p].next) dfs(e[p].num);
sort(a + 1, a + tot + 1); int now = t, top = 0;
for (int i = 1; i <= tot; i++) {
while (d[now] >= d[x] && a[i].v <= d[now]) {
while (top > 1 && slope(st[top - 1], st[top]) < slope(st[top], now)) top--;
st[++top] = now, now = fa[now];
}
chkmin(dp[a[i].x], query(a[i].x, top));
}
for (int p = head[t]; p; p = e[p].next) solve(e[p].num, siz[e[p].num]);
}
int main() {
int n, t; read(n), read(t);
for (int i = 2; i <= n; i++) {
ll v; read(fa[i]), read(v), add(fa[i], i, v);
read(p[i]), read(q[i]), read(lim[i]);
}
getdep(1); d[0] = -1;
memset(dp, 0x3f, sizeof(dp)); dp[1] = 0;
solve(1, n);
for (int i = 2; i <= n; i++) cout << dp[i] << '\n';
return 0;
}