[算法学习] 长链剖分
简介
长链剖分是跟dsu on tree类似的小\(trick\),可以资瓷维护子树中只与深度有关的信息。
并能达到线性的时间复杂度。
算法流程
对于每个点,记录重儿子\(heavy[u]\)表示深度最大的儿子,其余作为轻儿子。
这样我们可以得到若干条互不相交的长链。
在维护信息的过程中,我们先\(O(1)\)继承重儿子的信息,再暴力合并其余轻儿子的信息。
因为每一个点仅属于一条长链,且一条长链只会在链顶位置作为轻儿子暴力合并一次,所以复杂度是线性的。
但是我们发现,这个数组仿佛开不下(大雾),所以我们需要想想办法来解决。
有一个比较巧妙的方法,就是利用指针来实现。
下面以一道题为例。
CF1009F Dominant Indices
Description
给定一个以 \(1\) 为根, \(n\) 个节点的树。
设 \(d(u,x)\) 为 \(u\) 子树中到 \(u\) 距离为 \(x\) 的节点数。
求对于每一个点,最小的 \(k\) ,使得 \(d(u,k)\) 最大。
数据范围 \(1\le n\le 10^6\)。
Solution
我们先考虑如何暴力做。
定义\(f_{u,i}\)表示在 \(u\) 的子树内,到 \(u\) 的距离为 \(i\) 的点的个数。
那么,我们不难推出转移方程: \(f_{u,0}=1,f_{u,i}=\sum_{v\in son(x)}f_{v,i-1}\)。
复杂度:\(O(n^2)\),需要进行优化。
我们定义\(heavy[u]\)表示深度最大的儿子,\(len[u]\)表示\(x\)到儿子的最长距离。
不难发现\(dp\)第二维的 \(i\) 肯定不超过 \(len[u]\)。
为避免数组存不下的问题,我们采用指针来代替,即对于每条长链的链顶给它一个长度为\(len[x]\)的内存。
这样的好处在于,对于一条长链,我们可以直接让父亲点从子节点那里继承答案。
对于非重儿子的点,我们暴力合并链即可。
很显然,每条链只会被合并一次,因此复杂度是线性的。
复杂度:\(O(n)\),可以通过本题。
Code
解释一下\(id\)的作用:我们对于每条长链,给它开一个\(len[x]\)的内存,所以\(id\)是给每一个\(f[u]\)提供一段内存用的。
因为只在链顶会开内存,且每个点仅在一条重链内,所以空间复杂度是\(O(n)\)的。
但是实现起来,常数的确很大!\(n=1e6\)的时候大概要\(100+\)ms。
// Author: wlzhouzhuan
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define rint register int
#define rep(i, l, r) for (rint i = l; i <= r; i++)
#define per(i, l, r) for (rint i = l; i >= r; i--)
#define mset(s, _) memset(s, _, sizeof(s))
#define pb push_back
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
inline int read() {
int x = 0, neg = 1; char op = getchar();
while (!isdigit(op)) { if (op == '-') neg = -1; op = getchar(); }
while (isdigit(op)) { x = 10 * x + op - '0'; op = getchar(); }
return neg * x;
}
inline void print(int x) {
if (x < 0) { putchar('-'); x = -x; }
if (x >= 10) print(x / 10);
putchar(x % 10 + '0');
}
const int N = 1000005;
vector <int> adj[N];
void add(int u, int v) { adj[u].pb(v); }
int n;
int heavy[N], len[N];
void dfs1(int u, int fa) {
for (auto v: adj[u]) {
if (v == fa) continue;
dfs1(v, u);
if (len[v] > len[heavy[u]]) heavy[u] = v;
}
len[u] = len[heavy[u]] + 1;
}
int *f[N], tmp[N], *id = tmp, ans[N];
void dfs2(int u, int fa) {
f[u][0] = 1;
if (heavy[u]) {
f[heavy[u]] = f[u] + 1;
dfs2(heavy[u], u);
ans[u] = ans[heavy[u]] + 1;
}
for (auto v: adj[u]) {
if (v == fa || v == heavy[u]) continue;
f[v] = id, id += len[v];
dfs2(v, u);
for (rint j = 1; j <= len[v]; j++) {
f[u][j] += f[v][j - 1];
if (f[u][j] > f[u][ans[u]] || (f[u][j] == f[u][ans[u]] && j < ans[u])) {
ans[u] = j;
}
}
}
if (f[u][ans[u]] == 1) {
ans[u] = 0;
}
}
int main() {
n = read();
for (rint i = 1; i < n; i++) {
int u = read(), v = read();
add(u, v), add(v, u);
}
int root = 1; // 题目定义根为1
dfs1(root, 0);
f[1] = id, id += len[1];
dfs2(root, 0);
for (rint i = 1; i <= n; i++) {
printf("%d\n", ans[i]);
}
return 0;
}