CodeForces 1935F Andrey's Tree
赛后 15min 过题/ll。
删掉点 \(u\) 后树会分成若干棵子树。给每个子树一个编号,令 \(c_i\) 表示 \(i\) 所在子树的编号。然后题目要求一个类似最小生成树的东西。
既然要求最小生成树,那肯定先从 \(|a - b| = 1\) 选起。对于所有 \(i \in [1, u - 2] \cup [u + 1, n]\),连边 \((c_i, c_{i + 1})\),那么新图上最多有两个连通块。
新图只有一个连通块就直接做完了,否则 \((c_{u - 1}, c_{u + 1})\) 一定不在同一个连通块,合并它们的代价是 \(2\)。这样就是最优策略。
考虑怎么快速找到全部有用的 \((c_i, c_{i + 1})\) 的边。对于一对 \((i, i + 1)\),设 \(x = \text{lca}(i, i + 1)\)。发现只有当 \(u = x\) 时 \(i, i + 1\) 才都在 \(x\) 的子树中且在 \(x\) 的不同儿子的子树。设 \(u\) 的一个儿子 \(y\) 为 \(i\) 所在子树,那么 \(fa_i \to y\) 路径上的所有点满足 \(i\) 在它的一个儿子的子树,\(i + 1\) 在它父亲的子树。反过来对于 \(i + 1\) 也类似。
当 \(u = x\) 时可以用 vector
存全部满足的 \(i\);否则维护每个点 \(v\) 的子树中的使得 \(dep_z\) 最小的 \(i\)(这样就能尽可能使得 \(i + 1\) 在 \(v\) 父亲那棵子树)。用可撤销并查集维护连通块的合并。
总时间复杂度 \(O(n \log n)\)。
code
// Problem: F. Andrey's Tree
// Contest: Codeforces - Codeforces Round 932 (Div. 2)
// URL: https://codeforces.com/contest/1935/problem/F
// Memory Limit: 256 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mkp make_pair
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef long double ldb;
typedef pair<int, int> pii;
const int maxn = 200100;
const int logn = 20;
int n, ff[maxn], rk[maxn], tp;
pair<int*, int> stk[maxn << 2];
pii b[maxn];
vector<int> G[maxn];
vector<pii> T[maxn];
int fa[maxn], sz[maxn], son[maxn], dep[maxn], f[logn][maxn];
int top[maxn], dfn[maxn], tim, rnk[maxn];
pii g[logn][maxn];
inline pii qmin(int l, int r) {
int k = __lg(r - l + 1);
return min(g[k][l], g[k][r - (1 << k) + 1]);
}
int dfs(int u, int f, int d) {
fa[u] = f;
sz[u] = 1;
dep[u] = d;
int mx = -1;
for (int v : G[u]) {
if (v == f) {
continue;
}
::f[0][v] = u;
sz[u] += dfs(v, u, d + 1);
if (sz[v] > mx) {
son[u] = v;
mx = sz[v];
}
}
return sz[u];
}
void dfs2(int u, int tp) {
top[u] = tp;
dfn[u] = ++tim;
rnk[tim] = u;
if (!son[u]) {
return;
}
dfs2(son[u], tp);
for (int v : G[u]) {
if (!dfn[v]) {
dfs2(v, v);
}
}
}
inline int qlca(int x, int y) {
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) {
swap(x, y);
}
x = fa[top[x]];
}
if (dep[x] > dep[y]) {
swap(x, y);
}
return x;
}
inline int jump(int x, int k) {
for (int i = 18; ~i; --i) {
if (k & (1 << i)) {
x = f[i][x];
}
}
return x;
}
int find(int x) {
return ff[x] == x ? x : find(ff[x]);
}
inline bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return 0;
}
if (rk[x] <= rk[y]) {
stk[++tp] = mkp(ff + x, ff[x]);
ff[x] = y;
if (rk[x] == rk[y]) {
stk[++tp] = mkp(rk + y, rk[y]);
++rk[y];
}
} else {
stk[++tp] = mkp(ff + y, ff[y]);
ff[y] = x;
}
return 1;
}
void solve() {
scanf("%d", &n);
tim = tp = 0;
for (int i = 1; i <= n; ++i) {
ff[i] = i;
rk[i] = 0;
vector<int>().swap(G[i]);
vector<pii>().swap(T[i]);
fa[i] = sz[i] = son[i] = dep[i] = top[i] = dfn[i] = 0;
g[0][i] = mkp(n + 1, n + 1);
}
for (int i = 1, u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
G[u].pb(v);
G[v].pb(u);
}
dfs(1, -1, 1);
dfs2(1, 1);
for (int j = 1; j <= 18; ++j) {
for (int i = 1; i <= n; ++i) {
f[j][i] = f[j - 1][f[j - 1][i]];
}
}
for (int i = 1; i < n; ++i) {
int x = i, y = i + 1, z = qlca(i, i + 1);
if (dep[x] > dep[y]) {
swap(x, y);
}
if (dep[x] - dep[z] >= 2) {
g[0][dfn[x]] = min(g[0][dfn[x]], mkp(dep[z] + 1, i));
}
if (dep[y] - dep[z] >= 2) {
g[0][dfn[y]] = min(g[0][dfn[y]], mkp(dep[z] + 1, i));
}
if (x != z) {
T[z].pb(i, i + 1);
}
}
for (int j = 1; (1 << j) <= n; ++j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++i) {
g[j][i] = min(g[j - 1][i], g[j - 1][i + (1 << (j - 1))]);
}
}
for (int i = 1; i <= n; ++i) {
vector<pii> res;
int ans = 0;
for (pii p : T[i]) {
int x = p.fst, y = p.scd;
int u = jump(x, dep[x] - dep[i] - 1), v = jump(y, dep[y] - dep[i] - 1);
if (merge(u, v)) {
++ans;
res.pb(x, y);
}
}
for (int j : G[i]) {
if (j == fa[i]) {
continue;
}
pii p = qmin(dfn[j], dfn[j] + sz[j] - 1);
if (p.fst <= dep[i] && merge(j, i)) {
++ans;
res.pb(p.scd, p.scd + 1);
}
}
if (ans != (int)G[i].size() - 1) {
ans += 2;
res.pb(i - 1, i + 1);
}
printf("%d %d\n", ans, (int)res.size());
for (pii p : res) {
printf("%d %d\n", p.fst, p.scd);
}
putchar('\n');
while (tp) {
*stk[tp].fst = stk[tp].scd;
--tp;
}
}
}
int main() {
int T = 1;
scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}