UOJ-618 JOISC2021 聚会 2

Description

给定一颗 \(n\) 个点的树,点集为 \(V\)\(\forall i \in \{1, 2, \cdots, n\}\),求:

  • 对于一个 \(S\subseteq V\),将 \(S\) 中的点点权设为 \(1\),其他的点为 \(0\)。设 \(f(S)\) 为带权重心的个数。
  • \(\max\limits_{|S|=i} f(S)\)

Constraints

\(1\le n\le 2\cdot 10^5\)

Solution

我菜死了,什么点分治倍增我完全不会,那么只好乱胡了一个线段树合并。发现我这种方法好像有点少,那就随便写一个了。

首先是一个必要的转化:

  • 如果 \(i\) 为奇数答案必然为 \(1\)
  • 如果 \(i\) 为偶数,那么可以发现,如果有两个子树中带权的点数正好都为 \(\tfrac i 2\),那么两个子树根连成的路径(包括两端)都可以作为重心。而要答案尽可能大的话,那么就在这个无根树上找出两个 \(\ge \tfrac i 2\) 的子树即可,且两个根尽可能远。
    • 为了方便,考虑直接用“\(=\tfrac i 2\)”取代“\(\ge \tfrac i 2\)”,最后对答案数组做一遍后缀 \(\max\) 即可。

然后就是比较传统的路径问题,只不过这里的子树大小不是对于当前分治区域而言的,而是从整棵树来看。然后大概是什么有根树点分治,不太会就抛掉了。

考虑树上问题的另一个经典思路:线段树合并。为了实现转化题意中两个 size 配对的过程(这种说法并不准确,下文会提到),我们的线段树以 size 为下标,以最大可能的深度为对应值。

在当前点 \(x\)?,合并两个子树的线段树时,如果合并到叶子就可以更新答案了。由于两点间距离的计算还需要 LCA 的深度,我们在合并是顺便带上 \({dep}_x\)???。但这样存在一个问题,就是选取一个子树中的若干个点,但却不一定选取整个子树——相当于我们需要用这个子树的 \(dep_x\)? 去更新线段树上 \([1, {siz}_x]\)? 的每一个位置。解决方案:利用动态开点,打上区间更新的标记,并在下传时新建节点。

这样可以实现子树间的信息合并,但还不够:父亲方向上的我们尚未考虑。容易发现,上方的点可以选择 \([1, n-{siz}_x]\) 个,不妨设每种选择方式距离 \(x\) 最近的点为 \(fa_x\),如果实际上可以更远那会在上面更新。现在对于位置 \([1, n-{siz}_x]\) 我们在打一个上下标记,值为 \(dep_x -1\)。同样在叶子节点上统计对答案的贡献。

注意 pushdown 时应先下传区间更新标记,再下传上下标记,dfs 是也用这样的顺序。pushdown 内如果下传到叶子也要更新答案。最后一棵总线段树需要把标记传干净。其他细节在写的时候就能发现了。复杂度 \(O(n\log n)\)

Code

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
const int N = 2e5 + 5;
const int K = 50;
const int inf = 0x3f3f3f3f;

std::vector<int> adj[N];
int n, ans[N * 2];

int siz[N], dep[N];
void init(int x, int f) {
  siz[x] = 1, dep[x] = dep[f] + 1;
  for (int i = 0; i < (int)adj[x].size(); i++) {
    int y = adj[x][i];
    if (y == f) continue;
    init(y, x), siz[x] += siz[y];
  }
}

#define mid ((l + r) >> 1)
int lc[N * K], rc[N * K], tot;
int maxd[N * K], tag[N * K], fill[N * K];
inline void pushdown(int x, int l, int r) {
  if (fill[x]) {
    if (!lc[x]) lc[x] = ++tot;
    maxd[lc[x]] = std::max(maxd[lc[x]], fill[x]);
    fill[lc[x]] = std::max(fill[lc[x]], fill[x]);
    if (!rc[x]) rc[x] = ++tot;
    maxd[rc[x]] = std::max(maxd[rc[x]], fill[x]);
    fill[rc[x]] = std::max(fill[rc[x]], fill[x]);
    fill[x] = 0;
  }
  if (tag[x] != inf) {
    if (lc[x]) {
      tag[lc[x]] = std::min(tag[x], tag[lc[x]]);
      if (r - l == 1)
        ans[l * 2] = std::max(ans[l * 2], tag[lc[x]] - maxd[lc[x]] + 1);
    }
    if (rc[x]) {
      tag[rc[x]] = std::min(tag[x], tag[rc[x]]);
      if (r - l == 1)
        ans[r * 2] = std::max(ans[r * 2], tag[rc[x]] - maxd[rc[x]] + 1);
    }
    tag[x] = inf;
  }
}
void insert(int& x, int l, int r, int ql, int qr, int d) {
  if (ql > r || l > qr) return;
  if (!x) x = ++tot;
  if (ql <= l && r <= qr) {
    fill[x] = std::max(fill[x], d);
    maxd[x] = std::max(maxd[x], d);
    return;
  }
  pushdown(x, l, r);
  insert(lc[x], l, mid, ql, qr, d);
  insert(rc[x], mid + 1, r, ql, qr, d);
  maxd[x] = std::max(maxd[lc[x]], maxd[rc[x]]);
}
int merge(int x, int y, int l, int r, int cur) {
  if (!x || !y) return x | y;
  if (l == r) {
    ans[l * 2] = std::max(ans[l * 2], maxd[x] + maxd[y] - cur * 2 + 1);
    maxd[x] = std::max(maxd[x], maxd[y]);
    return x;
  }
  pushdown(x, l, r);
  pushdown(y, l, r);
  lc[x] = merge(lc[x], lc[y], l, mid, cur);
  rc[x] = merge(rc[x], rc[y], mid + 1, r, cur);
  maxd[x] = std::max(maxd[lc[x]], maxd[rc[x]]);
  return x;
}
int at(int x, int l, int r, int ql, int qr) {
  if (ql > r || l > qr || !x) return 0;
  if (ql <= l && r <= qr) return maxd[x];
  pushdown(x, l, r);
  return std::max(at(lc[x], l, mid, l, r), at(rc[x], mid + 1, r, l, r));
}
void update(int x, int l, int r, int ql, int qr, int dtop) {
  if (!x || ql > r || l > qr) return;
  if (ql <= l && r <= qr) {
    tag[x] = std::min(tag[x], dtop);
    if (l == r) ans[l * 2] = std::max(ans[l * 2], tag[x] - maxd[x] + 1);
    return;
  }
  pushdown(x, l, r);
  update(lc[x], l, mid, ql, qr, dtop);
  update(rc[x], mid + 1, r, ql, qr, dtop);
}
void flush(int x, int l, int r) {
  if (!x) return;
  if (l == r) {
    ans[l * 2] = std::max(ans[l * 2], maxd[x] - tag[x] + 1);
    return;
  }
  pushdown(x, l, r);
  flush(lc[x], l, mid);
  flush(rc[x], mid + 1, r);
}
#undef mid

int solve(int x, int f) {
  int root = 0;
  for (int i = 0; i < (int)adj[x].size(); i++) {
    int y = adj[x][i];
    if (y == f) continue;

    int sub = solve(y, x);
    root = merge(root, sub, 1, n, dep[x]);
  }
  insert(root, 1, n, 1, siz[x], dep[x]);
  if (x != 1)
    update(root, 1, n, 1, n - siz[x], dep[x] - 1);
  return root;
}

signed main() {
  memset(tag, inf, sizeof(tag));

  scanf("%d", &n);
  for (int i = 1, u, v; i < n; i++) {
    scanf("%d%d", &u, &v);
    adj[u].push_back(v);
    adj[v].push_back(u);
  }

  init(1, 0);
  int root = solve(1, 0);
  flush(root, 1, n);

  for (int i = n; i; --i) {
    ans[i] = std::max(ans[i], 1);
    if (!(i & 1)) ans[i] = std::max(ans[i], ans[i + 2]);
  }

  for (int i = 1; i <= n; i++)
    printf("%d\n", ans[i]);
  return 0;
}

同步于:https://strncmp.blog.luogu.org/solution-p7565

UOJ-618 JOISC2021 聚会 2

上一篇:短信宝发送


下一篇:音乐播放器foobox 6.1.6.5,基于foobar2000汉化版的CUI配置整合版 (2021-4-17)