彩色聖誕樹 題解

XuYueming發表於2024-10-10

前言

笑點解析:特殊性質 \(t\) 判錯了,掛了 \(15\)pts。

題意簡述

一棵 \(n\) 個結點的有根樹,\(1\) 為樹根,初始每個點都有不同的顏色。在接下來 \(m\) 次操作中,你需要維護這棵樹,有如下 \(3\) 種操作:

  1. \(u\) 到根這條鏈上每一個點的顏色覆蓋為新的一種顏色;
  2. 查詢 \(f(u, v)\),其中 \(f(u, v)\) 表示 \(u\)\(v\) 鏈上不同顏色數;
  3. 對於一個 \(u\) 子樹中的 \(v\),查詢 \(\max \limits _ {v \in \operatorname{subtree}(u)} f(1, v)\)

\(n, m \leq 2 \times 10 ^ 5\)

題目分析

由於每次覆蓋都是新開一個顏色,所以我們猜想 \(f(u, v)\)\(u\)\(v\) 鏈上顏色段數是相同的。

點選展開證明

考慮一種顏色,最初其在樹上出現總是一條鏈,且這條鏈的一端為另一端的祖先。考慮影響到這種顏色,當且僅當另一種顏色把鏈頂到鏈中某一個點(或者鏈底)覆蓋了。所以每種顏色在任何時刻,在樹上都是以一條連續的鏈存在。\(f(u, v)\) 不等於顏色段數,當且僅當有一種顏色多貢獻了顏色段數(即在 \(u\)\(v\) 間出現的形式為至少兩段),而我們證明了一種顏色時刻都是連續的一條鏈,所以這是不可能的。

誒誒誒,這不就成 原題 了嗎?甚至還是弱化版?你別急,你先別急,還有操作 \(3\) 呢。

操作 \(3\) 要求我們維護 \(f(1, u)\),並且能夠支援子樹查詢 \(\max\)。如果把樹拍到 dfs 序上,查詢簡單,考慮如何修改。

首先顏色段不好統計,根據經典小 trick,轉化為統計 \(1 \sim u\) 有多少 \(\operatorname{color}(v) \neq \operatorname{color}({\operatorname{fa}(v)})\),成了 \(v\) 上的一個點權。當然 \(1\) 的點權總是為 \(1\)

有個暴力的想法,沿用之前樹剖 + 線段樹的方式,我們新開一棵樹。覆蓋 \(1 \sim u\),就在樹上二分出第一個 \(\operatorname{color}(v) \neq \operatorname{color}({\operatorname{fa}(v)})\)\(v\),將原先和 \(\operatorname{fa}(v)\) 具有相同顏色的 \(v' \in \operatorname{son}(\operatorname{fa}(v))\) 的子樹做區間減一,把 \(v\) 的子樹做區間加一即可。邊界細節處理一下就可以了。樹上二分和區間加一隻 \(\log\) 沒什麼要說的,我們需要考慮會存在多少個 \(v\)

事實上,我們猜測這樣的 \(v\) 不多,均攤下來,一個可能的上界是 \(\mathcal{O}(\log n)\)

點選展開構造方式

考慮滿二叉樹,相當於一個 01-trie,每次往下走的時候,往和上一次訪問到這個結點不同的那個方向,這樣一定走滿 \(\Theta(\log n)\) 層來到葉子結點。

#include <cstdio>
#include <iostream>
#include <vector>
#include <random>
#include <algorithm>
#include <tuple>
#include <cassert>
using namespace std;

constexpr int n = 200000, m = 200000;
constexpr int p = __lg(n + 1);
constexpr int q = (1 << p) - 1;
constexpr int l = 1 << (p - 1);

vector<pair<int, int>> edges;
vector<tuple<int, int, int>> qrys, q1, q2;

mt19937 rnd(101108);

inline int rand(int l, int r) {
    uniform_int_distribution<int> dist(l, r);
    return dist(rnd);
}

int ls[n + 1], rs[n + 1];
bool lst[n + 1];

signed main() {
    freopen("yzh", "w", stdout);
    
    for (int u = 1; u <= q - l; ++u) {
        ls[u] = u << 1, rs[u] = u << 1 | 1;
        edges.emplace_back(u, ls[u]);
        edges.emplace_back(u, rs[u]);
    }
    for (int u = q + 1; u <= n; ++u) {
        int v;
        do v = rand(q - l + 1, u - 1); while (ls[v] && rs[v]);
        if (!ls[v] && !rs[v]) (rnd() & 1 ? ls : rs)[v] = u;
        else if (!ls[v]) ls[v] = u;
        else rs[v] = u;
        edges.emplace_back(u, v);
    }
    shuffle(edges.begin(), edges.end(), rnd);
    
    int leaf = 0;
    for (int i = 1; i <= n; ++i) leaf += !ls[i] && !rs[i];
    int t = max(1, m / leaf - rand(0, 2));
    for (int _ = 1; _ <= t; ++_) {
        for (int __ = 1; __ <= leaf; ++__) {
            int u = 1;
            while (ls[u] || rs[u]) {
                if (!rs[u] || (lst[u] && ls[u]))
                    lst[u] = false, u = ls[u];
                else
                    lst[u] = true, u = rs[u];
            }
            q1.emplace_back(1, u, 0);
        }
    }
    for (int _ = t * leaf + 1; _ <= m; ++_) {
        int op = rand(2, 3);
        if (op == 2) {
            q2.emplace_back(2, rand(1, n), rand(1, n));
        } else {
            q2.emplace_back(3, rand(1, n), 0);
        }
    }
    
    auto it1 = q1.begin(), it2 = q2.begin();
    for (int _ = 1; _ <= m; ++_) {
        if (it1 != q1.end() && it2 != q2.end()) {
            int $1 = q1.end() - it1, $2 = q2.end() - it2;
            if (rand(1, $1 + $2) <= $1)
                qrys.emplace_back(*it1++);
            else
                qrys.emplace_back(*it2++);
        } else if (it2 == q2.end())
            qrys.emplace_back(*it1++);
        else
            qrys.emplace_back(*it2++);
    }
    
    assert((int)edges.size() == n - 1);
    assert((int)qrys.size() == m);
    
    printf("%d %d %d\n", n, m, 0);
    for (auto [u, v]: edges) {
        if (rnd() & 1) swap(u, v);
        printf("%d %d\n", u, v);
    }
    for (auto [op, u, v]: qrys) {
        if (op == 2) printf("%d %d %d\n", op, u, v);
        else printf("%d %d\n", op, u);
    }
    return 0;
}

由於我不會證明,所以這個時間複雜度可能是錯誤的,歡迎指出。

如此,總的時間複雜度為 \(\mathcal{O}(n + q \log^2 n)\)

程式碼

#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;

const int MAX = 1 << 26;
char buf[MAX], *p = buf, obuf[MAX], *op = obuf;
#ifdef XuYueming
# define fread(_, __, ___, ____)
#else
# define getchar() *p++
#endif
#define putchar(x) *op++ = x
#define isdigit(x) ('0' <= x && x <= '9')
#define __yzh__(x) for (; x isdigit(ch); ch = getchar())
template <typename T>
inline void read(T &x) {
    x = 0; char ch = getchar(); __yzh__(!);
    __yzh__( ) x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename T>
inline void write(T x) {
    static short stack[10], top(0);
    do stack[++top] = x % 10; while (x /= 10);
    while (top) putchar(stack[top--] | 48);
    putchar('\n');
}

const int N = 200010;

int n, m, ccnt;
int tp[N << 1], tl[N << 1];
vector<int> edge[N];

int son[N], siz[N], dpt[N];
int top[N], fa[N];
int L[N], R[N], timer;
int dfn[N];

void dfs(int now) {
    siz[now] = 1;
    for (int to: edge[now]) if (to != fa[now]) {
        fa[to] = now, dpt[to] = dpt[now] + 1;
        dfs(to), siz[now] += siz[to];
        if (siz[to] > siz[son[now]]) son[now] = to;
    }
}

void redfs(int now, int tp) {
    dfn[L[now] = ++timer] = now;
    top[now] = tp;
    if (son[now]) redfs(son[now], tp);
    for (int to: edge[now]) if (to != fa[now] && to != son[now]) redfs(to, to);
    R[now] = timer;
}

namespace $1 {
    struct Segment_Tree {
        #define lson (idx << 1    )
        #define rson (idx << 1 | 1)
        
        struct Info {
            int l, r, cnt;
            friend Info operator + (const Info& a, const Info& b) {
                return { a.l, b.r, a.cnt + b.cnt - (a.r == b.l) };
            }
            inline Info swap() const {
                return { r, l, cnt };
            }
        };
        
        struct node {
            int l, r;
            int tag;
            Info info;
        } tree[N << 2];
        
        void build(int idx, int l, int r) {
            tree[idx] = { l, r, -1, { 0, 0, 0 } };
            if (l == r) {
                ++ccnt;
                tree[idx].info = { ccnt, ccnt, 1 };
                tp[ccnt] = tl[ccnt] = dfn[l];
                return;
            }
            int mid = (l + r) >> 1;
            build(lson, l, mid);
            build(rson, mid + 1, r);
            tree[idx].info = tree[lson].info + tree[rson].info;
        }
        
        void pushtag(int idx, int tag) {
            tree[idx].tag = tag;
            tree[idx].info = {tag, tag, 1};
        }
        
        void pushdown(int idx) {
            if (tree[idx].tag == -1) return;
            pushtag(lson, tree[idx].tag);
            pushtag(rson, tree[idx].tag);
            tree[idx].tag = -1;
        }
        
        void modify(int idx, int l, int r, int tag) {
            if (tree[idx].l > r || tree[idx].r < l) return;
            if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, tag);
            pushdown(idx);
            modify(lson, l, r, tag);
            modify(rson, l, r, tag);
            tree[idx].info = tree[lson].info + tree[rson].info;
        }
        
        Info query(int idx, int l, int r) {
            if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].info;
            pushdown(idx);
            if (r <= tree[lson].r) return query(lson, l, r);
            if (l >= tree[rson].l) return query(rson, l, r);
            return query(lson, l, r) + query(rson, l, r);
        }
        
        int query(int p) {
            return query(1, p, p).l;
        }
        
        #undef lson
        #undef rson
    } yzh;

    inline int lca(int u, int v) {
        while (top[u] != top[v]) {
            if (dpt[top[u]] < dpt[top[v]]) swap(u, v);
            u = fa[top[u]];
        }
        return dpt[u] < dpt[v] ? u : v;
    }

    inline int jump(int u, int p) {
        int res = u;
        while (top[u] != top[p])
            res = top[u], u = fa[top[u]];
        return u == p ? res : dfn[L[p] + 1];
    }

    inline void modify(int u, int v, int c) {
        while (top[u] != top[v]) {
            if (dpt[top[u]] < dpt[top[v]]) swap(u, v);
            yzh.modify(1, L[top[u]], L[u], c);
            u = fa[top[u]];
        }
        if (dpt[u] > dpt[v]) swap(u, v);
        yzh.modify(1, L[u], L[v], c);
    }

    inline Segment_Tree::Info query(int u, int v, bool) {
        Segment_Tree::Info res = {-1, -1, 0};
        while (top[u] != top[v]) {
            res = res + yzh.query(1, L[top[u]], L[u]).swap();
            u = fa[top[u]];
        }
        res = res + yzh.query(1, L[v], L[u]).swap();
        return res;
    }

    inline int query(int u, int v) {
        int p = lca(u, v);
        if (dpt[u] > dpt[v]) swap(u, v);
        if (u == p) {
            Segment_Tree::Info ans = query(v, u, true);
            return ans.cnt;
        } else {
            int q = jump(u, p);
            Segment_Tree::Info ans = query(u, q, true) + query(v, p, true).swap();
            return ans.cnt;
        }
    }
}

namespace $2 {
    struct Segment_Tree {
        #define lson (idx << 1    )
        #define rson (idx << 1 | 1)
        
        struct node {
            int l, r;
            int tag;
            int mx;
        } tree[N << 2];
        
        void pushup(int idx) {
            tree[idx].mx = max(tree[lson].mx, tree[rson].mx);
        }
        
        void build(int idx, int l, int r) {
            tree[idx] = { l, r, 0, 0 };
            if (l == r) return tree[idx].mx = dpt[dfn[l]] + 1, void();
            int mid = (l + r) >> 1;
            build(lson, l, mid);
            build(rson, mid + 1, r);
            pushup(idx);
        }
        
        void pushtag(int idx, int tag) {
            tree[idx].tag += tag;
            tree[idx].mx += tag;
        }
        
        void pushdown(int idx) {
            if (tree[idx].tag == 0) return;
            pushtag(lson, tree[idx].tag);
            pushtag(rson, tree[idx].tag);
            tree[idx].tag = 0;
        }
        
        void modify(int idx, int l, int r, int tag) {
            if (tree[idx].l > r || tree[idx].r < l) return;
            if (l <= tree[idx].l && tree[idx].r <= r) return pushtag(idx, tag);
            pushdown(idx);
            modify(lson, l, r, tag);
            modify(rson, l, r, tag);
            pushup(idx);
        }
        
        int query(int idx, int l, int r) {
            if (l > tree[idx].r || r < tree[idx].l) return 0;
            if (l <= tree[idx].l && tree[idx].r <= r) return tree[idx].mx;
            pushdown(idx);
            return max(query(lson, l, r), query(rson, l, r));
        }
        
        #undef lson
        #undef rson
    } yzh;
    
    void modify(int u, int c) {
        tp[c] = 1, tl[c] = u;
        for (int v; u; u = v) {
            int cu = $1::yzh.query(L[u]);
            v = fa[tp[cu]];
            if (v) yzh.modify(1, L[tp[cu]], R[tp[cu]], -1);
            if (u != tl[cu]) {
                int p = $1::jump(tl[cu], u);
                yzh.modify(1, L[p], R[p], 1);
                tp[cu] = p;
            }
        }
    }
    
    inline int query(int u) {
        return yzh.query(1, L[u], R[u]);
    }
}

signed main() {
    freopen("yzh", "r", stdin);
    freopen("xym", "w", stdout);
    fread(buf, 1, MAX, stdin);
    read(n), read(m), read(*new int);
    for (int i = 1, u, v; i < n; ++i) read(u), read(v), edge[u].push_back(v), edge[v].push_back(u);
    dfs(1), redfs(1, 1), $1::yzh.build(1, 1, n), $2::yzh.build(1, 1, n);
    for (int i = 1, op, u, v; i <= m; ++i) {
        read(op), read(u);
        if (op == 1) {
            ++ccnt;
            $2::modify(u, ccnt);
            $1::modify(1, u, ccnt);
        } else if (op == 2) {
            read(v);
            write($1::query(u, v));
        } else {
            write($2::query(u));
        }
    }
    fwrite(obuf, 1, op - obuf, stdout);
    return 0;
}

相關文章