UOJ618 聚会2 题解

题目传送门

题意: 有一棵 \(n\) 个点的无根树,对于每个 \(1\le k\le n\),求选 \(k\) 个点的最大权值。定义一种方案的权值为,满足“所有选中的点到该点的距离之和最小”的点的个数。\(n\le 2\times 10^5\)

首先可以发现,对于一种方案,对于当前点,沿一条边移动后,边对面的子树中选中的点距离 \(-1\),边同侧的子树中选中的点距离 \(+1\)。于是,如果有一条边两边的选中点数不同,则一定要往点数多的那边移动。于是容易发现合法的点个数即为两边点数相同的边数 \(+1\)

首先,当 \(k\) 为奇数答案显然为 \(1\),因为不可能存在两边点数相同的边。以下考虑点数为偶数。

为了最大化合法点的数量,一定是将 \(\frac{k}{2}\) 个点放到某个子树内,剩下的 \(\frac{k}{2}\) 放到另一个子树内,答案即为这两棵子树的“根”的距离。

可以发现,合法的子树的根一定组成树上的一个连通块,且 \(k\) 越大限制越严,连通块不断缩小。每次 \(k\) 的答案即为连通块的直径。

于是,做法就浮出水面了:对于每个点,处理出它在哪个 \(k\) 被删掉,扔到 vector 里,从小到大枚举 \(k\),统计答案后把 vector 里的点删掉。

思路就是这样,实现上有一些必要的算法:

  1. 对于删点动态维护直径,可以通过线段树来做。线段树的区间 \([l,r]\) 保存钦定端点编号在 \([l,r]\) 内的直径两端,则每次合并两个儿子时,可以暴力求 \(\binom{4}{2}\) 遍 dis 合并出父亲区间的答案。这是因为两个点集的并的直径端点一定是从两个点集各自直径的端点中选出的。结合 \(O(n\log n)-O(1)\) LCA 即可将复杂度做到 \(n\log n\)
  2. 对于计算在哪个 \(k\) 被删掉,感性理解一下可以发现,\(k\) 超过 \(sz_u\) 时一定会被扔掉,超过某个 \(n-sz_v(v\in son_u)\) 时也会被扔掉,所以取 \(\min\) 即可。

From Unique_Hanpi, changed by cxm1024

#include <bits/stdc++.h>
#define lowbit(x) (x & -x)
#define eb emplace_back
#define pb push_back
#define mp make_pair
using namespace std;

typedef long long ll;
const int N = 2e5+5;
const int Mod = 998244353;

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') f = c == '-' ? -1 : 1, c = getchar();
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

int n;
int siz[N], dep[N], dfn[N], nfd[N];
int seq[N * 2], p[N];
int st[N * 2][19], lg2[N * 2];

vector<int> T[N], del[N];

void dfs(int x, int fa) {
    int lim = 1e9;
    siz[x] = 1;
    dep[x] = dep[fa] + 1;
    nfd[dfn[x] = ++dfn[0]] = x;
    seq[p[x] = ++seq[0]] = dfn[x];
    for (auto son : T[x]) {
        if (son == fa) continue;
        dfs(son, x);
        siz[x] += siz[son];
        lim = min(lim, n - siz[son]);
        seq[++seq[0]] = dfn[x];
    }
    lim = min(siz[x], lim);
    del[lim].pb(x);
}

inline int dis(int x, int y) {
    if (!x || !y) return -1;
    int l = p[x], r = p[y];
    if (l > r) swap(l, r);
    int len = lg2[r - l + 1];
    int lca = nfd[min(st[r][len], st[l + (1 << len) - 1][len])];
    return dep[x] + dep[y] - 2 * dep[lca] + 1;
}

namespace segT {
    struct node {
        int len, u, v;
        node() {}
        node(int _u, int _v): u(_u), v(_v) { len = dis(u, v); }
    } T[N << 2];

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

    inline node max(const node &a, const node &b) {
        return a.len < b.len ? b : a;
    }

    inline void push_up(int p) {
        int lu = T[ls].u, lv = T[ls].v, ru = T[rs].u, rv = T[rs].v;
        T[p] = max(max(max(max(max(T[ls], T[rs]),
                   node(lu, ru)), node(lu, rv)), node(lv, ru)), node(lv, rv));
    }

    void build(int p, int l, int r) {
        if (l == r) {
            T[p] = node(l, r);
            return;
        }
        int mid = (l + r) >> 1;
        build(ls, l, mid);
        build(rs, mid + 1, r);
        push_up(p);
    }

    void upd(int p, int l, int r, int gk) {
        if (l == r) {
            T[p] = node(0, 0);
            return;
        }
        int mid = (l + r) >> 1;
        if (mid >= gk) upd(ls, l, mid, gk);
        else upd(rs, mid + 1, r, gk);
        push_up(p);
    }

    #undef ls
    #undef rs
}

int main() {
    // freopen("island.in", "r", stdin);
    // freopen("island.out", "w", stdout);
    n = read();
    for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        T[u].pb(v), T[v].pb(u);
    }

    dfs(1, 0);

    for (int i = 2; i <= seq[0]; i++) lg2[i] = lg2[i >> 1] + 1;
    for (int i = 1; i <= seq[0]; i++) {
        st[i][0] = seq[i];
        for (int j = 1; j <= lg2[i]; j++)
            st[i][j] = min(st[i][j - 1], st[i - (1 << j - 1)][j - 1]);
    }

    segT::build(1, 1, n);

    for (int i = 1; i <= n; i++) {
        if (i & 1) puts("1");
        else {
            printf("%d\n", max(1, segT::T[1].len));
            for (int j : del[i >> 1]) segT::upd(1, 1, n, j);
        }
    }
    return 0;
}
posted @ 2023-03-01 23:33  曹轩鸣  阅读(96)  评论(0编辑  收藏  举报