1. 程式人生 > >LOJ #2434. 「ZJOI2018」歷史(LCT)

LOJ #2434. 「ZJOI2018」歷史(LCT)

ado cto args cout ble http != 題意 main

題意

click here

題解

我們首先考慮答案是個什麽樣的東西, 不難 發現每個點可以單獨計算它的貢獻。

令每個點 \(i\) 崛起次數為 \(a_i\)

假設一個點子樹的 \(\sum a_i\) 分別為 \(b_1,b_2,\dots,b_k\) ,令 \(S = a_i + \sum b_j\)

那麽這個點的答案為
\[ \min (2(S - \max(\max\{b_j\}, a_i), S - 1) \]
至於為什麽是這樣可以簡單說明下:

\(S - 1\) :顯然是這個點的答案的上界,除了第一次,後面每一次最多對這個點貢獻一次。

\(2(S - \max(\max\{b_j\}, a_i))\)

:不難發現,我們總可以找到一種方案使得 \(S - \max\) 那種與 剩下的 \(\max(\max\{b_j\}, a_i)\) 交錯出現,使得這個答案取得上界,然後每次會存在兩種貢獻。

這樣我們就可以得到一個 \(O(n)\)\(dp\) 了。


我們考慮如何動態維護這個 \(dp\)

不難發現這個操作及其類似於 Link_Cut_Tree 中的 Access 操作。

我們令 \(b_u = \sum_{v \in child(u)} a_v\) 也就是 \(u\) 的子樹 \(a\) 和。

我們考慮維護這個東西,不難發現每次給一個點的 \(a_u\) 加上 \(v\) ,相當於把這個點到根的 \(b_u\)

加上 \(v\)

然後考慮如何維護一個點的貢獻,如果 \(u\) 存在一個兒子 \(v\) 使得 \(b_v \times 2 > b_u + 1\) 那麽我們定義 \(v \to u\) 為實邊。

其余的邊都為虛邊。不難發現這些實邊會對於 \(u\) 存在 \(2(b_u - b_v)\) 的貢獻。( \(a\) 不可能存在貢獻,因為 \(b_v\) 已經占據一半了)

然後虛邊的貢獻就是 \(\min(b_u - 1, 2(b_u - a_u))\)

這個可以自己列列不等式,討論討論就行了。

然後我們每次 Access 操作就是將鏈上的一些點加權,並且更換虛實邊就行了,重新計算貢獻就行了。

這個直接用支持加法標記的 LCT 維護就行了。

不難發現一個點到根的實邊最多是 \(\log w\) 條,因為每條實邊會使得權值至少翻倍。

所以最後復雜度就是 \(O(n + q\log w)\) 的。

代碼

不太會寫的話還是建議看看代碼的。。

#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 Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << x << endl
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)

using namespace std;

typedef long long ll;
inline bool chkmin(ll &a, ll b) {return b < a ? a = b, 1 : 0;}
inline bool chkmax(ll &a, ll 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 ("374.in", "r", stdin);
    freopen ("374.out", "w", stdout);
#endif
}

const int N = 4e5 + 1e3;

int n, m;
vector<int> G[N];

int son[N];

ll Ans, ans[N], b[N], a[N];
inline void ReCalc(int u) {
    Ans -= ans[u]; 
    ans[u] = son[u] ? 2 * (b[u] - b[son[u]]) : b[u] - 1; 
    if (a[u] * 2 > b[u] + 1) ans[u] = 2 * (b[u] - a[u]);
    Ans += ans[u];
}

#define ls(o) ch[o][0]
#define rs(o) ch[o][1]

namespace Link_Cut_Tree {

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

    inline bool get(int o) { return o == rs(fa[o]); }

    inline bool is_root(int o) { return o != ls(fa[o]) && o != rs(fa[o]); }

    inline void Rotate(int v) {
        int u = fa[v], t = fa[u], d = get(v);
        fa[ch[u][d] = ch[v][d ^ 1]] = u;
        fa[v] = t; if (!is_root(u)) ch[t][rs(t) == u] = v;
        fa[ch[v][d ^ 1] = u] = v;
    }

    ll tag[N];
    inline void Add(int o, ll uv) { if (o) b[o] += uv, tag[o] += uv; }

    inline void Push_Down(int o) {
        if (!tag[o]) return ;
        Add(ls(o), tag[o]); 
        Add(rs(o), tag[o]); tag[o] = 0;
    }

    inline void Push_All(int o) {
        if (!is_root(o)) Push_All(fa[o]); Push_Down(o);
    }

    inline void Splay(int o) {
        Push_All(o);
        for (; !is_root(o); Rotate(o))
            if (!is_root(fa[o])) Rotate(get(o) != get(fa[o]) ? o : fa[o]);
    }

    inline int Get_Root(int o) {
        while (ls(o)) Push_Down(o), o = ls(o); return o;
    }

    inline void Access(int o, int uv) {
        for (int t = 0; o; o = fa[t = o]) {
            Splay(o); 
            b[o] += uv; Add(ls(o), uv);

            if (son[o]) {
                Push_All(son[o]);
                if (b[son[o]] * 2 <= b[o] + 1) son[o] = rs(o) = 0;
            }
            int to = Get_Root(t);
            if (b[to] * 2 > b[o] + 1) son[o] = to, rs(o) = t;
            ReCalc(o);
        }
    }

}

void Dfs_Init(int u, int fa = 0) {
    Link_Cut_Tree :: fa[u] = fa; b[u] = a[u]; int to = 0;
    for (int v : G[u]) if (v != fa) {
        Dfs_Init(v, u); b[u] += b[v];
        if (b[v] > b[to]) to = v;
    }
    if (b[to] * 2 > b[u]) son[u] = Link_Cut_Tree :: rs(u) = to; ReCalc(u);
}

int main () {

    File();

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

    printf ("%lld\n", Ans);
    For (i, 1, m) {
        int pos = read(), val = read();
        a[pos] += val; Link_Cut_Tree :: Access(pos, val);
        printf ("%lld\n", Ans);
    }

    return 0;
}

LOJ #2434. 「ZJOI2018」歷史(LCT)