「ceoi 2009」harbingers


link。

朴素 dp 大约就是 \(f_x=f_y+v_x\times(d_x-d_y)+s_x\)\(y\)\(x\) 的祖先。这个式子可以斜率优化,在以 \(d_y\) 为横坐标,\(f_y\) 为纵坐标的坐标系中,斜率为 \(v_x\)

我们应该用单调栈维护一个到根的树链。由于回溯的时候 \(v_x\) 不一定单调,取 \(f_x\) 的 dp 值时我们应该在完整的凸壳上二分。

插入决策点时,按照普通序列上的斜率优化弹栈(朴素)。很多人的问题是回溯还原栈状态的复杂度,其实你真正修改的值只有插入进去的位置,如果你没有使用 STL 的话弹栈的操作实际上是没有进行的,他们保留在栈中,只需要将栈顶指针还原即可,每次回溯的个数是 \(O(1)\) 级别的。

实际插入决策点时,因为时间复杂度的问题需要二分插入位置。如果你以乘代除,注意开 __int128

#include
#define cmin(x, y) x = std::min(x, y)
#define cmax(x, y) x = std::max(x, y)
#define fors(i, l, r, ...) for(int i = (l), REP##i = (r), ##__VA_ARGS__; i <= REP##i; ++i)
#define dfors(i, r, l, ...) for(int i = (r), REP##i = (l), ##__VA_ARGS__; i >= REP##i; --i)
typedef long long ll;
int n, q[100100], l = 1, r;
ll f[100100], v[100100], s[100100], d[100100];
std::vector> e[100100];
void pre_dfs(const int x, int fa) {
    for(const auto [y, w] : e[x]) if(y != fa) d[y] = d[x]+w,pre_dfs(y, x);
}
ll up(const int i, const int j) { return f[j]-f[i]; }
ll down(const int i, const int j) { return d[j]-d[i]; }
ll cal(const int i, const int j) { return f[j]+v[i]*(d[i]-d[j])+s[i]; }
int bisect_optimal_decision(const ll k) {
    int l = ::l, r = ::r, res = l; l++;
    for(int mid; l <= r;) {
        mid = (l+r)>>1;
        if(up(q[mid-1], q[mid]) <= (__int128)k*down(q[mid-1], q[mid])) l = mid+1,res = mid;
        else r = mid-1;
    }
    return q[res];
}
int bisect_insert_position(const int x) {
    int l = ::l, r = ::r, res = l; l++;
    for(int mid; l <= r;) {
        mid = (l+r)>>1;
        if((__int128)up(q[mid-1], q[mid])*down(q[mid], x) <= (__int128)up(q[mid], x)*down(q[mid-1], q[mid])) l = mid+1,res = mid;
        else r = mid-1;
    }
    return res;
}
void dp_dfs(const int x, const int fa) {
    if(x != 1) f[x] = cal(x, bisect_optimal_decision(v[x]));
    int _r = r, t, tt;
    r = bisect_insert_position(x);
    t = r+1,tt = q[r+1],q[++r] = x;
    for(const auto [y, w] : e[x]) if(y != fa) dp_dfs(y, x);
    q[t] = tt,r = _r;
}
signed main() {
    std::ios_base::sync_with_stdio(0);
    std::cin.tie(0);
    std::cin >> n;
    fors(i, 1, n-1, x, y, z) {
        std::cin >> x >> y >> z;
        e[x].emplace_back(y, z);
        e[y].emplace_back(x, z);
    }
    fors(i, 2, n) std::cin >> s[i] >> v[i];
    pre_dfs(1, 0),dp_dfs(1, 0);
    fors(i, 2, n) std::cout << f[i] << " \n"[i == n];
    return 0;
}