题解:P10866 [HBCPC2024] Colorful Tree

· · 题解

简要题意

你需要维护一个 n 个点的树,初始时所有点都是白色。有 m 次操作,每次操作给定一条树上简单路径,你需要将路径上所有点染成黑色。

你需要求出最长的同色路径(注意路径上所有点都必须同色,路径长度定义为这条路径上的数量,包含端点)。

## 思路 本来以为是路径端点颜色相同,我还以为是经典题目,写完后测了样例发现不对,这才发现题目要求是路径上所有点的颜色……不过这道题也不难。 首先我们先分开求最长黑色路径和最长白色路径,先求最长黑色路径。一种经典的错误思路是维护黑色 / 白色点集,直接维护点集直径。由于黑色 / 白色点**不一定**构成一个连通块,所以求出来的直径中间可能会夹杂其他颜色的点。 顺着这个思路想,我们可以就维护同色连通块,然后维护这个连通块的直径。维护连通块的首选是并查集。我们可以将直径信息存在并查集的根节点上。但是染色可能会出现一条链染多次的情况。 由于我没有想到这玩意也可以并查集,所以我用到了一个比较劣的方法,我们维护一个点第一个被染成黑色的时刻,显然这个可以离线后倒序枚举询问转换为树上染颜色,可以用重链剖分配合颜色段均摊简单维护。 然后我们按照第一个被染成黑色的时刻顺序依次考虑每一个点,先暴力枚举这个点直接相邻的其他点,如果这个点同样也是黑色点,且还没有和这个点合并为一个连通块,那么我们就可以直接合并,合并并查集的同时也要合并直径信息,在更新直径信息的时候更新当前时刻最大直径。注意这样得到的答案需要做一遍前缀最大值才是真的答案(因为答案显然单调,且可能会有其他连通块未被统计)。 对于最长白色路径,如果按照时间顺序做就是删点,比较难做,我们离线倒着做,就变成了加点,和上面做法几乎一样,就不提了。 至于直径合并,这是经典 trick,两个点集的直径端点,一共四个,我们选出两个点,使得这两个点构成的路径长度最大,这个最长路径就是两个点集的并的直径。 最后将两种答案取 $\max$ 即可,时间复杂度单组数据 $O(n\log n+m\log^2 n)$。瓶颈在重链剖分配合颜色段均摊。 ## 代码 5.04 KB 大常数代码,建议不要学。 ```cpp #include <bits/stdc++.h> //#define int long long using namespace std; const int N = 2e5 + 5; namespace odt{ struct node{ int l,r; mutable int v; node(int L, int R, int V){l = L, r = R, v = V;} bool operator<(const node &x) const {return l < x.l;} }; set<node> tree; auto split(int pos){ auto it = tree.lower_bound(node(pos, 0, 0)); if(it != tree.end() && it -> l == pos) return it; it--; int l = it -> l, r = it -> r, v = it -> v; tree.erase(it); tree.insert(node(l, pos - 1, v)); return tree.insert(node(pos, r, v)).first; } void assign(int l, int r, int v){ auto R = split(r + 1), L = split(l); tree.erase(L, R); tree.insert(node(l, r, v)); } int query(int l, int r){ auto R = split(r + 1), L = split(l); for(auto it = L;it != R;it++){ return it -> v; } } } struct edge{ int nxt, to; } g[N << 1]; int head[N], ec; void add(int u, int v){ g[++ec].nxt = head[u]; g[ec].to = v; head[u] = ec; } int dep[N], siz[N], father[N], son[N], top[N], seg[N], rev[N]; void dfs1(int u,int fa){ siz[u] = 1;father[u] = fa; dep[u] = dep[fa] + 1; for(int i=head[u];i;i=g[i].nxt){ int v = g[i].to; if(v == fa) continue; dfs1(v, u); siz[u] += siz[v]; if(siz[v] > siz[son[u]]) son[u] = v; } } void dfs2(int u,int fa){ if(son[u]){ top[son[u]] = top[u]; seg[son[u]] = (++seg[0]); rev[seg[0]] = son[u]; dfs2(son[u], u); } for(int i=head[u];i;i=g[i].nxt){ int v = g[i].to; if(v == fa || son[u] == v) continue; top[v] = v; seg[v] = (++seg[0]); rev[seg[0]] = v; dfs2(v, u); } } int lca(int x, int y){ while(top[x] != top[y]){ if(dep[top[x]] < dep[top[y]]) swap(x, y); x = father[top[x]]; } return dep[x] > dep[y] ? y : x; } void UpdateTree(int x,int y,int z){ while(top[x] != top[y]){ if(dep[top[x]] < dep[top[y]]) swap(x, y); odt::assign(seg[top[x]], seg[x], z); x = father[top[x]]; } if(dep[x] > dep[y]) swap(x, y); odt::assign(seg[x], seg[y], z); } int dis(int x, int y){ int b = lca(x, y); int a = dep[x] + dep[y] - 2 * dep[b] + 1; return a; } int fa[N]; int find(int x){ return fa[x] == x ? x : fa[x] = find(fa[x]); } int n, m; struct option{ int x, y; } opt[N]; bool col[N]; int black[N], white[N]; struct diameter{ int x, y; } d[N]; diameter operator+(const diameter& x, const diameter& y){ auto _ = [&](int x, int y){ return make_pair(dis(x, y), make_pair(x, y)); }; vector<pair<int,pair<int,int> > > bkt = { _(x.x, x.y), _(y.x, y.y), _(x.x, y.x), _(x.y, y.y), _(x.x, y.y), _(x.y, y.x) }; auto ret = *max_element(bkt.begin(), bkt.end()); return {ret.second.first, ret.second.second}; } void solve(){ cin >> n >> m; for(int i=1;i<=n;i++) fa[i] = i; for(int i=1;i<n;i++){ int u, v; cin >> u >> v; add(u, v); add(v, u); } seg[0] = seg[1] = 1; top[1] = rev[1] = 1; dfs1(1, 0);dfs2(1, 0); odt::tree.clear(); odt::tree.insert(odt::node(1, n, m + 1)); for(int i=1;i<=m;i++){ cin >> opt[i].x >> opt[i].y; } for(int i=m;i>=1;i--) UpdateTree(opt[i].x, opt[i].y, i); vector<pair<int,int> > vp; for(int i=1;i<=n;i++){ int tim = odt::query(seg[i], seg[i]); vp.push_back({tim, i}); } sort(vp.begin(), vp.end()); for(auto& [tim, u] : vp){ if(tim > m) continue; col[u] = 1; d[u] = {u, u}; for(int i=head[u];i;i=g[i].nxt){ int v = g[i].to; if(!col[v]) continue; if(find(u) != find(v)){ d[find(u)] = d[find(u)] + d[find(v)]; black[tim] = max(black[tim], dis(d[find(u)].x, d[find(u)].y)); fa[find(v)] = find(u); } } } for(int i=1;i<=m;i++) black[i] = max(black[i], black[i - 1]); reverse(vp.begin(), vp.end()); for(int i=1;i<=n;i++) fa[i] = i, col[i] = 0; for(auto& [tim, u] : vp){ col[u] = 1; d[u] = {u, u}; for(int i=head[u];i;i=g[i].nxt){ int v = g[i].to; if(!col[v]) continue; if(find(u) != find(v)){ d[find(u)] = d[find(u)] + d[find(v)]; white[tim - 1] = max(white[tim - 1], dis(d[find(u)].x, d[find(u)].y)); fa[find(v)] = find(u); } } } for(int i=m-1;i;i--) white[i] = max(white[i], white[i + 1]); for(int i=1;i<=m;i++) cout << max(black[i], white[i]) << '\n'; } void clear(){ for(int i=1;i<=n;i++){ head[i] = dep[i] = siz[i] = father[i] = son[i] = top[i] = seg[i] = rev[i] = 0; } for(int i=1;i<=m;i++){ white[i] = black[i] = col[i] = 0; } ec = 0; } signed main(){ ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); int t; cin >> t; while(t--) solve(), clear(); return 0; } // Written by xiezheyuan ```