1 条题解
-
1
题目大意:给定一棵 个节点的无根树(通常默认 为根),对于每个节点 ,找到一个相对深度 ,使得在 的子树中,到 距离为 的节点数量最多。如果有多个 满足条件,取最小的 。
我们可以定义状态 表示:在以 为根的子树中,距离 深度为 的节点个数。
转移方程非常简单:
同时 (节点本身)。
每次计算完 的所有元素后,我们只需要遍历一遍 数组,找出最大值对应的最小下标 即可。
vector<int> tr[N]; int f[N][N]; // f[u][j] 表示以 u 为根的子树中,相对深度为 j 的节点数 int ans[N]; void dfs(int u, int fa) { f[u][0] = 1; ans[u] = 0; for (int v : tr[u]) { if (v == fa) continue; dfs(v, u); for (int j = 1; j <= n; j++) { f[u][j] += f[v][j - 1]; } int maxv = 1; for (int j = 1; j <= n; j++) { if (f[u][j] > maxv) { maxv = f[u][j]; ans[u] = j; } } } }瓶颈:状态有两维,空间复杂度和时间复杂度都是 ,对于 的数据量来说,肯定会 MLE 和 TLE。
核心优化原理:长链剖分
如果仔细观察转移方程:,你会发现 的信息其实可以直接“平移”给 。这就引出了长链剖分的核心思想:把最长的子树路径直接继承,其余短分支再暴力合并。
和重链剖分(根据子树节点数量划分重儿子)不同,长链剖分是根据子树的深度来划分重儿子的。对于每个节点 ,我们把它所有儿子中,子树深度最深
的那个儿子称为“重儿子(长儿子)”,其余的称为“轻儿子”。
内存复用技术(空间和时间双重优化)
因为重儿子的深度是最深的,我们可以让节点 直接继承重儿子 的 数组。具体怎么继承呢?利用指针!
让 的首地址等于 的首地址往前挪一位:
f[v] = f[u] + 1。这样, 天然就是 ,我们连赋值的操作都省了,时间复杂度直接降为 !
对于其他的轻儿子,我们再老老实实开辟新的内存空间计算,计算完后暴力累加到 中。
因为每个节点只会位于一条长链中,合并轻链的代价实际上就是整棵树所有长链的总长度,而所有长链长度之和正好等于 。因此,整体的时间复杂度被降到了 。
假设有这样一棵树:
1 / \ 2 4 / 3内存分配与计算过程:
- 我们为链
1 -> 2 -> 3分配一段连续的内存,比如叫mem[0..2]。- 让指针 指向
mem[0]; - 让指针 指向
mem[1](即f[1] + 1); - 让指针 指向
mem[2](即f[2] + 1);
- 让指针 指向
- 计算 3: 。此时
mem[2] = 1。 - 计算 2: 。此时
mem[1] = 1。因为 和 共享内存,此时 自动就是 的值,也就是 。目前 ,最大值为 ,为了下标最小,节点 的答案取 。 - 计算 4(轻儿子): 另起炉灶,分配一块新内存
mem[3]。。 - 计算 1: 。此时
mem[0] = 1。继承重儿子 的结果,此时 自动包含了 (分别对应深度 )。接着,我们把轻儿子 的信息合并进来: 是 的轻儿子,距离差为 ,所以把 累加到 上:。此时 。最大值是 ,出现在下标 处,所以 。
具体实现上有两种方法:
- 可以将二维数组 改为一个一维的指针数组,并使用一个额外的一维内存池动态分配内存。
#include <iostream> #include <vector> using namespace std; #define N 1000005 int n; vector<int> tr[N]; // maxdep 记录子树的最大深度,son 记录重儿子(最深儿子) int maxdep[N], son[N]; // 核心优化:指针数组 f 和一维内存池 mem int *f[N]; int mem[N]; int *ptr = mem; // ptr 指向当前内存池未使用的起始位置 int ans[N]; // 第一次 DFS:预处理每个节点的深度和长儿子 void dfs(int u, int fa) { maxdep[u] = 1; for (int v : tr[u]) { if (v == fa) continue; dfs(v, u); // 如果子节点的深度更大,更新当前节点的最大深度和重儿子 if (maxdep[v] + 1 > maxdep[u]) { maxdep[u] = maxdep[v] + 1; son[u] = v; } } } // 第二次 DFS:利用指针计算答案 void dp(int u, int fa) { f[u][0] = 1; // 节点 u 自身(距离为 0 的节点数为 1) // 如果有重儿子,先处理重儿子 if (son[u]) { // 【核心操作】让重儿子的数组直接使用 u 的内存(往后错一位) f[son[u]] = f[u] + 1; dp(son[u], u); // 继承重儿子的最优答案,因为深度往下平移了 1,所以答案也要 +1 ans[u] = ans[son[u]] + 1; // 细节处理:如果继承过来后最大节点数仍然为 1,根据题意应该取最小深度 0 if (f[u][ans[u]] == 1) ans[u] = 0; } // 处理所有轻儿子 for (int v : tr[u]) { if (v == fa || v == son[u]) continue; // 给轻儿子重新分配独立的一段内存,长度为其最大深度 f[v] = ptr; ptr += maxdep[v]; dp(v, u); // 将轻儿子的信息暴力合并到当前节点 u for (int j = 1; j <= maxdep[v]; j++) { f[u][j] += f[v][j - 1]; // 轻儿子的深度 j-1 相当于 u 的深度 j // 实时更新当前节点的最优答案 if (f[u][j] > f[u][ans[u]] || (f[u][j] == f[u][ans[u]] && j < ans[u])) { ans[u] = j; } } } } int main() { cin >> n; for (int i = 1, u, v; i < n; i++) { cin >> u >> v; tr[u].push_back(v); tr[v].push_back(u); } dfs(1, 0); // 为根节点 1 分配第一段初始内存 f[1] = ptr; ptr += maxdep[1]; dp(1, 0); // 输出每个节点的答案 for (int i = 1; i <= n; i++) { cout << ans[i] << "\n"; } return 0; }- 由于长链剖分同样可以使得同一条重链上的 连续,那么也可以直接使用节点的 作为内存地址,这样省去了手动分配地址的操作,代码变得更加简洁优美。
#include <iostream> #include <vector> using namespace std; #define N 1000005 int n; vector<int> tr[N]; int f[N]; int maxdep[N], son[N], dfn[N]; int ans[N]; void dfs(int u, int fa) { maxdep[u] = 1; for (int v : tr[u]) { if (v == fa) continue; dfs(v, u); if (maxdep[u] < maxdep[v] + 1) { maxdep[u] = maxdep[v] + 1; son[u] = v; } } } void dp(int u, int fa) { static int idx = 0; dfn[u] = ++idx; f[dfn[u]] = 1; if (son[u]) { f[dfn[son[u]]] = f[dfn[u]] + 1; dp(son[u], u); ans[u] = ans[son[u]] + 1; if (f[dfn[u] + ans[u]] == 1) ans[u] = 0; } // 暴力合并轻儿子 for (int v : tr[u]) { if (v == fa || v == son[u]) continue; dp(v, u); for (int j = 1; j <= maxdep[v]; j++) { f[dfn[u] + j] += f[dfn[v] + j - 1]; if (f[dfn[u] + j] > f[dfn[u] + ans[u]] || f[dfn[u] + j] == f[dfn[u] + ans[u]] && j < ans[u]) { ans[u] = j; } } } } int main() { cin >> n; for (int i = 1, u, v; i < n; i++) { cin >> u >> v; tr[u].push_back(v); tr[v].push_back(u); } dfs(1, 0); dp(1, 0); for (int i = 1; i <= n; i++) cout << ans[i] << '\n'; return 0; } - 我们为链
- 1
信息
- ID
- 2672
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 5
- 标签
- (无)
- 递交数
- 25
- 已通过
- 7
- 上传者