3 春季大扫除 题解

春季大扫除

题面

小y要对一棵 \(n\) 个节点,边权为 1 的树进行大扫除,她清理的过程是这样的:

  • 选中一对叶子节点
  • 将选中的叶子结点间的路径清理,清理的代价即为路径长度

为了保护叶子,每个叶子节点都只能选一次

为了增加难度,小y对树进行了 \(m\) 改造,在第 \(i\) 次改造中,她在原始树的某些节点下添加了共 \(D_i\) 个节点!

对于每次改造,求将每条边都至少清理一次的最小代价;

注意:每次改造是独立的,每次都是在原始的树上进行改造

\(1 \le n ,m \le 10^5\)

\(1 \le \sum D_i \le 10^5\)

题解

手模样例发现,对于某个子树而言,如果子树内有奇数个叶子节点,那么子树根节点上面的这条边一定会经过一次,否则会经过两次,不会经过更多次,因为经过更多次一定不优,所以我们只需考虑这两种情况。

假设子树内叶子节点数为奇数的子树个数为 \(cnt\) ,不难发现,答案即为 \((n - 1) \times 2 - cnt\) ,也就是先假设每条边都经过两次,然后减去经过一次的边的数量即可。

那么如何动态维护这个叶子节点数为奇数的子树个数呢?

考虑插入一个点时,受影响的是从这个点到根节点上的点,所以我们可以用树剖来维护这个。

时间复杂度 \(O(n \log^2 n)\)

code

这道题还卡常,实现的一些细节要注意,避免常数过大

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>

using namespace std;

namespace michaele {

    #define ls (p << 1)
    #define rs (p << 1 | 1)

    const int N = 1e5 + 10, M = N << 1;

    int n, m;
    int h[N], ver[M], ne[M], tot;
    int du[N], qr[N], cnt_leaf;
    bool leaf[N], vis[N];
    pair <int, int> st[N * 20];
    int tp;

    inline void add (int x, int y) {
        ver[ ++ tot] = y;
        ne[tot] = h[x];
        h[x] = tot; 
    }

    struct node {
        int val;
        bool rev;
    } t[N << 2];

    inline void update (int p) { t[p].val = t[ls].val + t[rs].val; }
    void build (int p, int l, int r) {
        if (l == r) return;
        int mid = (l + r) >> 1;
        build (ls, l, mid);
        build (rs, mid + 1, r);
        update (p);
    }
    
    inline void pushdown (int p, int l, int r) {
        if (!t[p].rev) return;
        int mid = (l + r) >> 1;
        t[ls].rev ^= 1;
        t[rs].rev ^= 1;
        t[ls].val = mid - l + 1 - t[ls].val;
        t[rs].val = r - mid - t[rs].val;
        t[p].rev = 0;
    }

    void modify (int p, int l, int r, int x, int y) {
        if (x <= l && r <= y) {
            t[p].rev ^= 1;
            t[p].val = r - l + 1 - t[p].val;
            return;
        }
        pushdown (p, l, r);
        int mid = (l + r) >> 1;
        if (x <= mid) modify (ls, l, mid, x, y);
        if (mid < y) modify (rs, mid + 1, r, x, y);
        update (p);
    }

    int siz[N], dep[N], son[N], fa[N];
    int id[N], top[N], tim;

    void dfs1 (int x, int f) {
        fa[x] = f;
        siz[x] = 1;
        dep[x] = dep[f] + 1;
        for (int i = h[x]; i; i = ne[i]) {
            int y = ver[i];
            if (y == f) continue;
            dfs1 (y, x);
            siz[x] += siz[y];
            if (siz[y] > siz[son[x]]) son[x] = y;
        }
    }

    void dfs2 (int x, int ftop) {
        id[x] = ++ tim;
        top[x] = ftop;
        if (!son[x]) return;
        dfs2 (son[x], ftop);
        for (int i = h[x]; i; i = ne[i]) {
            int y = ver[i];
            if (y == fa[x] || y == son[x]) continue;
            dfs2 (y, y);
        }
    }

    inline void change (int x, int y) {
        while (top[x] != top[y]) {
            if (dep[top[x]] < dep[top[y]]) swap (x, y);
            modify (1, 1, n, id[top[x]], id[x]);
            st[ ++ tp] = {id[top[x]], id[x]};
            x = fa[top[x]];
        }
        if (x == y) return;
        if (dep[x] > dep[y]) swap (x, y);
        modify (1, 1, n, id[son[x]], id[y]);
        st[ ++ tp] = {id[son[x]], id[y]};
    }

    void solve () {

        ios :: sync_with_stdio (0);
        cin.tie (0);
        cout.tie (0);

        cin >> n >> m;
        for (int i = 1; i < n; i ++) {
            int x, y;
            cin >> x >> y;
            add (x, y);
            add (y, x);
            du[x] ++;
            du[y] ++;
        }

        for (int i = 1; i <= n; i ++) {
            if (du[i] == 1) {
                leaf[i] = 1;
                cnt_leaf ++;
            } else leaf[i] = 0;
        }

        dfs1 (1, 0);
        dfs2 (1, 1);
        build (1, 1, n);

        for (int i = 1; i <= n; i ++) {
            if (leaf[i]) {
                change (i, 1);
            }
        }

        for (int i = 1; i <= m; i ++) {
            int d;
            cin >> d;
            int now_n = n + d, now_leaf = cnt_leaf;
            tp = 0;
            
            for (int j = 1; j <= d; j ++) {
                cin >> qr[j];
                if (leaf[qr[j]] && !vis[qr[j]]) {
                    // 某个叶子第一次被顶替
                    vis[qr[j]] = 1;
                } else {
                    change (qr[j], 1);
                    now_leaf ++;
                }
            }
            int now_cnt = t[1].val + d;
            if (now_leaf & 1) cout << -1 << endl;
            else cout << (now_n - 1) * 2 - now_cnt << endl;

            // 还原,被卡常了
            for (int j = 1; j <= d; j ++) {
                // change (v[j], 1);
                if (leaf[qr[j]] && vis[qr[j]]) {
                    vis[qr[j]] = 0;
                    // change (v[j], 1);
                }
            }
            // 用栈记录一下修改的区间,从而减少一个log
            for (int j = 1; j <= tp; j ++) {
                modify (1, 1, n, st[j].first, st[j].second);
            }
        }
    }
}

int main () {

    // freopen ("test/test.in", "r", stdin);
    // freopen ("test/test.out", "w", stdout);
    

    michaele :: solve ();


    return 0;
}
posted @ 2025-11-03 10:15  michaele  阅读(9)  评论(0)    收藏  举报