1. 程式人生 > >Codeforces Round #463 F. Escape Through Leaf (李超線段樹合併)

Codeforces Round #463 F. Escape Through Leaf (李超線段樹合併)

聽說正解是啥 set啟發式合併+維護凸包+二分 根本不會啊 , 只會 李超線段樹合併 啦 ...

題意

給你一顆有 \(n\) 個點的樹 , 每個節點有兩個權值 \(a_i, b_i\) .

\(u\) 跳到 \(v\) 的代價是 \(a_u \times b_v\) . 你需要計算每個節點跳到葉子的最小代價 .

\((n \le 10^5, -10^5 \le a_i, b_i \le 10^5)\)

題解

我們首先考慮一個很容易的 \(dp\) , 令 \(dp_i\)\(i\) 跳到葉子的最小代價 .

那麼顯然有一個轉移 此處 \(v\)\(u\) 的後代 .

\[\displaystyle dp_u = \min_v \{a[u] \times b[v] + dp_v\}\]

暴力轉移是 \(O(n^2)\) 的顯然無法接受 .

那麼考慮優化 , 不難發現這個轉移就是 李超線段樹上求多條直線 \(y=kx+b\)\(x=k\) 最值的形式 .

\((k = b[v], x = a[u], b=dp_v)\)

那麼顯然可以考慮用李超線段樹維護這個 \(dp\) .

對於樹上的每個點 , 可以用一顆李超線段樹維護這個點子樹的所有直線資訊 .

然後我們只需要考慮合併幾顆子樹資訊了 , 不難發現是 套路的 線段樹合併 . (這樣時間空間複雜度都正確了?)

我們直接同時遍歷兩顆線段樹 , 然後把其中一顆當前區間的優勢直線暴力插入另外一顆線段樹 .

最後把子樹全都合併上來後 , 直接詢問出 \(dp_u\)

就行了 , 然後再插入到這顆線段樹中去 .

由於詢問 \(a_i\) 可能為負數 , 而線段樹不太好維護負數 , 我們考慮插入直線和詢問的時候都向右平移 \(lim = 10^5\) 長度 .

原來的直線 \(y=kx+b\) 就變成了 \(y'=k(x-lim)+b=kx+(b-k\cdot lim)\) 了 .

(也許可以維護負數 diversion 說可以)

最後瞎分析時間複雜度 \(O(\sum minsize \log n) = O(n \log ^2 n)\) ... (錯了的話大佬幫我指正啊qwq)

這道題還是比較好寫的 ...

程式碼

#include <bits/stdc++.h>
#define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Set(a, v) memset(a, v, sizeof(a))
#define debug(x) cout << #x << ':' << x << endl
using namespace std;

inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;}

inline int read() {
    int x = 0, fh = 1; char ch = getchar();
    for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1;
    for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
    return x * fh;
}

void File() {
#ifdef zjp_shadow
    freopen ("F.in", "r", stdin);
    freopen ("F.out", "w", stdout);
#endif
}

const int N = 2e5 + 1e3, Lim = 1e5 + 5;
typedef long long ll;

struct Line { ll k, b; int id; ll func(int x) { return k * x + b; } };

inline bool Cmp(Line a, Line b, int x) { if (!a.id) return true; return a.func(x) > b.func(x); }

int rt[N];

const int Maxn = N * 30;
#define lson ls[o], l, mid
#define rson rs[o], mid + 1, r
struct Chao_Segment_Tree {
    Line Adv[Maxn]; int ls[Maxn], rs[Maxn], Size;

    Chao_Segment_Tree () {Size = 0;};

    void Insert(int &o, int l, int r, Line uv) {
        if (!o) o = ++ Size;
        int mid = (l + r) >> 1;
        if (Cmp(Adv[o], uv, mid)) swap(Adv[o], uv);

        if (l == r || Adv[o].k == uv.k || !uv.id) return ;
        double x = (double)(Adv[o].b - uv.b) / (uv.k - Adv[o].k);
        if (x < l || x > r) return ;

        if (uv.k > Adv[o].k) Insert(lson, uv); else Insert(rson, uv);
    }

    int Merge(int x, int y, int l, int r) {
        if (!x || !y) return x + y;
        Insert(x, l, r, Adv[y]);
        int mid = (l + r) >> 1;
        ls[x] = Merge(ls[x], ls[y], l, mid);
        rs[x] = Merge(rs[x], rs[y], mid + 1, r);
        return x;
    }
    
    Line Query(int o, int l, int r, int qp) {
        if (l == r) return Adv[o];
        int mid = (l + r) >> 1;
        Line tmp = (qp <= mid) ? Query(lson, qp) : Query(rson, qp);
        return Cmp(tmp, Adv[o], qp) ? Adv[o] : tmp;
    }

} T;

int n, A[N], B[N]; vector<int> G[N];

ll dp[N];

inline void Insert(int ver) {
    ll k = B[ver], b = dp[ver] - Lim * k;
    T.Insert(rt[ver], 1, Lim * 2, (Line) {k, b, ver});
}

inline ll Query(int ver) {
    int tmp = T.Query(rt[ver], 1, Lim * 2, A[ver] + Lim).id;
    return 1ll * A[ver] * B[tmp] + dp[tmp];
}

void Dp(int u, int fa) {
    for (int v : G[u]) if (v ^ fa) 
        Dp(v, u), rt[u] = T.Merge(rt[u], rt[v], 1, Lim * 2);
    dp[u] = Query(u); Insert(u);
}

int main () {
    File();

    n = read();
    For (i, 1, n) A[i] = read();
    For (i, 1, n) B[i] = read();

    For (i, 1, n - 1) {
        int u = read(), v = read();
        G[u].push_back(v); G[v].push_back(u);
    }

    Dp(1, 0); For (i, 1, n) printf ("%lld%c", dp[i], i == iend ? '\n' : ' ');

    return 0;
}