1. 程式人生 > >LOJ#2434. 「ZJOI2018」歷史 lct

LOJ#2434. 「ZJOI2018」歷史 lct

題意

  • 給你一顆樹上每個點的 a c c e s s access 次數,計算輕重鏈切換次數的最大值.

程式碼能力還是太弱了啊我,什麼題看懂後自己還是寫不出來,以後要練下自己碼力了。

首先要發現一個性質,那就是一個點的選擇是無後效性的,就是對於每個點貢獻的時候,只跟它的所有子樹的大小有關,然後每個點的貢獻就可以這樣算 f u = v a

l u + f a v =
x
f v f_u=val_u+\sum_{fa_v=x}f_v, 其中 v a l u = m i n ( S i z e u 1 , ( S i z e u m a x S i z e v ) × 2 ) val_u=min(Size_u-1,(Size_u-maxSize_v)×2) 這個東西舉幾個栗子自己感性理解一下就好了,那麼這樣子我們就可以獲得不帶修改的 30 p t s 30pts 了。

現在我們來考慮帶修改怎麼做,我們定義一個點的所有兒子中,滿足 2 × S i z e v > S i z e u + 1 2×Size_v>Size_u+1 的兒子為實兒子,顯然這樣的兒子只有一個,或者沒有。我們類比樹鏈剖分複雜度的證明可以發現,一個點到根節點只有 log n \log n 條輕邊,那麼我們可以用 L i n k C u t T r e e LinkCutTree 來維護這棵樹,每次修改的時候暴力往上找到所有輕邊即可,為什麼這樣複雜度是對的呢,因為題目的修改只有給一個權值加上一個數,那麼如果它原來是實兒子現在仍舊是實兒子而不會變成虛兒子,那麼這樣子一個點的實兒子變化的時候就不用去遍歷它的每一個兒子來考慮,因為它只能從虛兒子變成實兒子,那麼就可以了,複雜度 O ( n log n ) O(n\log n)

有一些需要注意的地方,就修改的時候為了方便把當前點的貢獻存下來,更新的時候減去當前點貢獻加上修改後的貢獻,還有就是要維護一個點子樹和減去實兒子和自己權值的值,因為這樣的話可以快速維護修改,又因為一個點在 l c t lct 中的左兒子是在原樹中自己的祖先,所以計運算元樹和的時候一定要減去左兒子權值,然後就是裸的模板了

Codes

#include <bits/stdc++.h>

#define pb push_back
#define ll long long
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
#define sx (S[x] - S[ls(x)]) 
#define rel(x) (x == rs(fa[x]))
#define isroot(x) (ls(fa[x]) ^ x && rs(fa[x]) ^ x)

using namespace std;

const int N = 4e5 + 10;

vector<int> G[N];

ll ans, S[N], Rs[N], res[N], a[N];

int ch[N][2], fa[N], n, q; 

void pushup(int x) {
    S[x] = S[ls(x)] + S[rs(x)] + Rs[x] + a[x];
}

void rotate(int x) {
    int dad = fa[x], f = rel(x);
    if (!isroot(dad)) ch[fa[dad]][rel(dad)] = x; 
    fa[x] = fa[dad], ch[dad][f] = ch[x][f ^ 1];
    fa[ch[dad][f]] = dad, ch[x][f ^ 1] = dad;
    pushup(dad), pushup(fa[dad] = x);
}

void splay(int x) {
    for (; !isroot(x); rotate(x)) if (!isroot(fa[x]))
        rotate(rel(x) == rel(fa[x]) ? fa[x] : x);
}

void update(int x) {
    ans -= res[x]; 
    res[x] = rs(x) ? (sx - S[rs(x)]) * 2 : sx - 1;
    if ((a[x] << 1) > sx + 1) res[x] = (sx - a[x]) << 1; 
    ans += res[x];
}

void access(int x, int z) {
    for (int y = 0; x; update(x), x = fa[y = x]) {
        splay(x), S[x] += z, *((y ? Rs : a) + x) += z;
        if ((S[rs(x)] << 1) < sx + 1) 
            Rs[x] += S[rs(x)], rs(x) = 0;
        if ((S[y] << 1) > sx + 1) 
            rs(x) = y, Rs[x] -= S[rs(x)];
    }
    printf("%lld\n", ans);
}

void dfs(int x, int dad) {
    int now = 0; fa[x] = dad, S[x] = a[x];
    for (auto v : G[x]) if (v ^ dad) {
        dfs(v, x), S[x] += S[v];
        if (S[v] > S[now]) now = v; 
    }
    if ((S[now] << 1) > S[x] + 1) rs(x) = now; 
    Rs[x] = S[x] - a[x] - S[rs(x)], update(x);
}

int main() {
#ifdef ylsakioi
    freopen("2434.in", "r", stdin);
    freopen("2434.out", "w", stdout);
#endif

    int x, y; 

    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; ++ i) 
        scanf("%lld", &a[i]);
    for (int i = 1; i < n; ++ i) {
        scanf("%d%d", &x, &y);
        G[x].pb(y), G[y].pb(x);
    }

    dfs(1, 0), printf("%lld\n", ans);
    while (q --) scanf("%d%d", &x, &y), access(x, y);

    return 0;
}