维护连通块
现有一棵 个节点的树。我们构建一最小的连通块 ,包含编号在 中的所有节点。多次询问,每次对于任意一对 ,询问关于这连通块的信息。
如果可以离线/可持久化,此类问题有一通用思路:用扫描线维护“ 固定时不同 构成连通块的信息”。若 ,则 ;对于一固定的 ,我们维护 分别取 (向左拓展)时,连通块的增量。对于点 ,我们记录 表示 固定时使得 的最大的 。假设已经对于 维护好了;现在拓展到 。
若 ,则 是
- 在 到 的简单路径上,且
的充分必要条件。
考虑任意 。由于 是极小的,故将 “加入” 得到 ,只需找到 和连通块在树上最接近节点之间的路径,将路径上所有点加入连通块;由树的性质,此路径是通向 中任意节点的必经之路,也即 的最短路径的某一前缀。又当 时, 就是该路径本身;再有 ,故本命题得证。
因此,我们需将整条路径上的点的最大出现时间整体覆盖成 ( 本身除外,它是 )。可以使用珂朵莉树和树剖维护连续的 相同的一段。设 表示连续段个数。每次覆盖操作会对 条重链执行“删除若干连续段,加入 个连续段”的操作。又 ,则 的总变化量是 的。因此若用对数级别有序关联式容器(如 std::map
),总时间复杂度为 。
例题 – MOCK PTS- 20231122 C – 树
给定大小为 的树。 次询问。给定 ,计算
实际上是维护边的最大出现时间。如果我们对同一 维护所有 的 ,并称其为序列的一个版本,则求的就是版本号 时序列 内元素的历史版本和。
考虑区间覆盖 的过程。对于一个将要被删除的连续段,设其原最大出现时间为 ,其长度为 。我们将它从 提升到 ,意味着“在 及以后的版本中, 中的所有 都有多 条边在 中”。对 的所有点加上 即可。使用线段树或树状数组,维护区间加、区间求历史版本和。时间复杂度 。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
#include <bits/stdc++.h> using namespace std; #define inl inline #define scd second #define fst first #define empb emplace_back #define all(p) begin (p), end (p) using ll = long long; using ull = unsigned long long; using pint = pair <int, int>; constexpr int N = 1e5 + 10, G = 1<<18; int n, m, fa[N], siz[N], hson[N], top[N], dep[N], dfn[N], dtot; ll ans[N]; vector <int> e[N]; vector <pint> qu[N]; map <int, int> g; void dfs (int x, int fr) { dep[x] = dep[fr] + 1; siz[x] = 1, fa[x] = fr; for (const int y : e[x]) { if (y == fr) continue; dfs (y, x), siz[x] += siz[y]; if (siz[y] > siz[hson[x]]) hson[x] = y; } } void decomp (int x, int tp) { top[x] = tp; dfn[x] = ++dtot; if (hson[x]) decomp (hson[x], tp); for (const int y : e[x]) if (y != hson[x] && y != fa[x]) decomp (y, y); } struct BIT { ll c[N], d[N]; inl void __add (int x, ll dt) { const ll wdt = dt * x; for (; x <= n; x += x & -x) c[x] += dt, d[x] += wdt; } inl void add (int l, int r, ll dt) { __add (l, dt); __add (r + 1, -dt); } inl ll __ask (int x) const { ll s = 0, ws = 0; for (int y = x; y > 0; y &= y - 1) s += c[y], ws += d[y]; return s * (x + 1) - ws; } inl ll ask (int l, int r) const { return __ask (r) - __ask (l - 1); } } bit, wbit; int main () { /* MOCK NOIP 20231122 吴秋实 */ // freopen ("tree.in", "r", stdin); // freopen ("tree.out", "w", stdout); scanf ("%d", &n); for (int i = 1, x, y; i < n; ++i) scanf ("%d%d", &x, &y), e[x].empb (y), e[y].empb (x); dfs (1, 0); decomp (1, 1); scanf ("%d", &m); for (int i = 0, l, r; i < m; ++i) scanf ("%d%d", &l, &r), qu[r].empb (l, i); g[1] = g[n + 1] = 0; for (int x = 2; x <= n; ++x) { int u = x, v = x - 1; auto idou = [&] (int l, int r, int fr) { // 从 t0 = fr - 1 移动到 x - 1。 const int len = r - l + 1; bit.add (max (fr, 1), x - 1, len); wbit.add (max (fr, 1), x - 1, (ll) len * x); // 历史版本和拆分常数贡献。 }; auto activ = [&] (int l, int r) { // 珂朵莉树核心代码。 auto pt = g.upper_bound (l), it = prev (pt), qt = g.upper_bound (r); while (it != qt) { const int p = it->fst, q = pt->fst - 1, c = it->scd; idou (max (p, l), min (q, r), c); g.erase (it); it = pt++; if (l > p) g[p] = c; if (r < q) g[r + 1] = c; } g[l] = x; }; while (top[u] != top[v]) { const int p = top[u], q = top[v]; if (dep[p] >= dep[q]) { activ (dfn[p], dfn[u]); u = fa[p]; } else { activ (dfn[q], dfn[v]); v = fa[q]; } } if (dep[u] > dep[v]) swap (u, v); if (u != v) activ (dfn[u] + 1, dfn[v]); const ll s = bit.__ask (x), ws = wbit.__ask (x); for (const auto &[l, id] : qu[x]) ans[id] = (s - bit.__ask (l - 1)) * (x + 1) - (ws - wbit.__ask (l - 1)); } for (int i = 0; i < m; ++i) printf ("%lld\n", ans[i]); return 0; }
维护虚树
记 表示 中所有节点构成的虚树。有 ,自然有 ,且 仍成立。我们记 表示最大的 使得 。
我们试图通过类似方式维护 确定时所有 的点集。不同于维护连通块,这下并不是 间简单路径上的所有节点都能得到更新,且不一定有 。我们需要一些更强的性质,方便精确更新 。
记从 到 的有向简单路径上节点依序为 。则 。
仍然考虑 时, 的逐步扩张——每次变成原先的超集。这条性质就很显然了。
记 。记从 到 的有向简单路径上节点依序为 。则 的充分必要条件是
- ,或
- ,即相同 连续段的最低节点。
。在 到 路径子树上的所有节点,其与 的 必然为 ;在 所有祖先的子树内的所有节点,其在 中的存在不受到 的影响。现在只需考虑所有 受 加入的影响。
若 ,则只可能是 ,且 在 的与 不同的子树中,且此时 和 是 子树中唯二的两个在 中的节点。仍考虑 和 以 递减的顺序逐步扩张。,正说明存在这样一个 ;且由 的单调性, 应是 和 在 子树内的唯一一个节点。故而, 的加入使得 。
仿照上文维护 的方式,我们只需在删除相同 连续段时,维护其底端节点 的 ,将其赋为 即可。时间复杂度不变。
例题 – MOCK PTS- 20231124 B – 传奇特级法术树
给定以 为根的大小为 的树。每条边有权值。 次询问,求 所有节点有多少种不同的深度。
我们记 表示 的深度。“将 从 提升到 ”意味着在 及以后的版本, 将在 的所有 中出现。维护每种深度的最大出现时间,若从 更新到 则将答案序列的 区间加一即可。时间复杂度 。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
#include <bits/stdc++.h> using namespace std; #define inl inline #define scd second #define fst first #define empb emplace_back #define all(p) begin (p), end (p) using ll = long long; using ull = unsigned long long; using pint = pair <int, int>; constexpr int N = 1e5 + 10, M = 5e5 + 10; int n, m, tc, ans[M], last[N], seq[N]; ll tt[N], d[N]; int siz[N], hson[N], top[N], fa[N], dep[N], dfn[N], dtot; int head[N]; struct query { int l, nxt; } qu[M]; vector <pint> e[N]; map <int, int> g; void dfs (int x, int fr) { siz[x] = 1; fa[x] = fr; dep[x] = dep[fr] + 1; for (const auto &[y, z] : e[x]) { if (y == fr) continue; d[y] = d[x] + z; dfs (y, x); siz[x] += siz[y]; if (siz[y] >= siz[hson[x]]) hson[x] = y; } } void decomp (int x, int tp) { top[x] = tp; dfn[x] = ++dtot; if (hson[x]) decomp (hson[x], tp); for (const auto &[y, z] : e[x]) if (y != fa[x] && y != hson[x]) decomp (y, y); } struct BIT { int c[N]; inl void __add (int x, int dt) { for (; x <= n; x += x & -x) c[x] += dt; } inl void add (int l, int r) { __add (l, 1); __add (r + 1, -1); } inl int ask (int x) const { int res = 0; for (; x > 0; x -= x & -x) res += c[x]; return res; } } bit; inl int rev (int x, int n = ::n) { return n - x + 1; } int main () { /* MOCK PTS 20231124 吴秋实 */ // freopen ("tree.in", "r", stdin); // freopen ("tree.out", "w", stdout); scanf ("%d%d", &n, &m); for (int i = 1, x, y, z; i < n; ++i) { scanf ("%d%d%d", &x, &y, &z); e[x].empb (y, z); e[y].empb (x, z); } dfs (1, 0); decomp (1, 1); memcpy (tt, d + 1, n<<3); sort (tt, tt + n); tc = unique (tt, tt + n) - tt; for (int x = 1; x <= n; ++x) { dfn[x] = rev (dfn[x]); seq[dfn[x]] = lower_bound (tt, tt + tc, d[x]) - tt; } for (int i = 1, l, r; i <= m; ++i) { scanf ("%d%d", &l, &r); if (l == r) ans[i] = 1; else qu[i] = { l, head[r] }, head[r] = i; } g[1] = g[n + 1] = 0; for (int x = 2; x <= n; ++x) { int u = x, v = x - 1, _c = 0; auto activ = [&] (int l, int r, auto escal) { auto qt = g.upper_bound (r), pt = g.upper_bound (l), it = prev (pt); while (it != qt) { const int c = it->scd, p = it->fst, q = pt->fst - 1; escal (max (l, p), c, _c); g.erase (it); it = pt++; if (p < l) g[p] = c; if (q > r) g[r + 1] = c; } g[l] = x; }; auto escal = [&] (int idx, int c, int &_c) { if (_c == c) return; else _c = c; const int num = seq[idx], t = c - 1; if (last[num] < t) { bit.add (rev (t), rev (last[num] + 1)); last[num] = t; } }; while (top[u] != top[v]) { if (dep[top[u]] >= dep[top[v]]) { activ (dfn[u], dfn[top[u]], escal); u = fa[top[u]]; } else { activ (dfn[v], dfn[top[v]], [] (int, int, int&) {}); v = fa[top[v]]; } } if (dfn[u] < dfn[v]) activ (dfn[u], dfn[v] - 1, escal); else if (dfn[v] < dfn[u]) activ (dfn[v], dfn[u] - 1, [] (int, int, int&) {}); escal (max (dfn[u], dfn[v]), x, _c); escal (dfn[x], x + 1, _c); for (int id = head[x]; id; id = qu[id].nxt) ans[id] = bit.ask (rev (qu[id].l)); } for (int i = 1; i <= m; ++i) printf ("%d\n", ans[i]); return 0; }