*【学习笔记】(18) 长链剖分
长链剖分
1.算法简介与性质
长链剖分本质上就是另外一种链剖分方式。
长链剖分与重链剖分有相通之处,后者是将 子树大小 最大的儿子作为重儿子,前者则是将 子树深度 最大的儿子作为重儿子。可见两者只是换了一个剖分形式。
长链剖分有如下性质:
- 性质 1:每个节点所在长链末端为其子树内最深节点。根据定义可知。
- 性质 2: 一个节点的
级祖先所在长链长度一定不小于 。- 设
的 级祖先为 ( 级祖先,向上跳 到达的祖先),如果 所在的长链小于 , 那么它所在的链一定不是长链,显然 这条链更优。那么 所在的重链长度至少为 ,性质成立。
- 设
- 性质 3:从根节点到任意叶子节点经过的轻边条数不超过
,这比重链剖分 稍劣一些。- 如果一个点
从一条重链跳到了另外一条重链上,那么跳跃到的这条重链的长度不会小于之前的重链长度。
那么在最坏的情况下,重链长度分别为 ,也就是最多跳跃 次。
- 如果一个点
2.应用:树上 级祖先
查询
首先跳到
#include<bits/stdc++.h> #define ui unsigned int #define ll long long using namespace std; const int N = 5e5 + 67; ui s; inline ui get(ui x) { x ^= x << 13, x ^= x >> 17, x ^= x << 5; return s = x; } int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, q, rt, lg; ll ans; int f[N][20], d[N], son[N], top[N], mxd[N], h[N]; int tot, Head[N], to[N], Next[N]; vector<int> up[N], down[N]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void dfs1(int x){ d[x] = d[f[x][0]] + 1, mxd[x] = 1; for(int i = 1; i <= lg; ++i) f[x][i] = f[f[x][i - 1]][i - 1]; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; dfs1(y); mxd[x] = max(mxd[x], mxd[y] + 1); if(mxd[son[x]] < mxd[y]) son[x] = y; } } void dfs2(int x, int topf){ if(x == topf) up[x].resize(mxd[x]), down[x].resize(mxd[x]); down[topf][d[x] - d[topf]] = x, top[x] = topf; if(son[x]) dfs2(son[x], topf); for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == son[x]) continue; dfs2(y, y); } } int main(){ n = read(), q = read(), s = read(), lg = log2(n); for(int i = 1; i <= n; ++i) !(f[i][0] = read()) ? (rt = i, void()) : add(f[i][0], i); dfs1(rt), dfs2(rt, rt); for(int i = 1; i <= n; ++i){ if(top[i] != i) continue; for(int j = 0, cur = i; j < mxd[i] && cur; ++j, cur = f[cur][0]) up[i][j] = cur; } for(int i = 1; i <= n; ++i) for(int j = 0; j <= lg; ++j) if(i >> j & 1) h[i] = j; for(int i = 1, las = 0; i <= q; ++i){ int x = (get(s) ^ las) % n + 1, k = (get(s) ^ las) % d[x]; x = !k ? x : f[x][h[k]], k -= (k == 0 ? 0 : (1 << h[k])); int t = top[x]; if(d[x] - k >= d[t]) las = down[t][d[x] - k - d[t]]; else las = up[t][d[t] - (d[x] - k)]; ans ^= 1ll * i * las; } printf("%lld\n", ans); return 0; }
3. 应用:优化深度相关的 DP
3.1 一般形式
长链剖分的价值主要体现在能优化树上 与深度有关 的 DP。如果子树内 每个深度仅有一个信息,就可以使用长链剖分优化。一般形式如:设
3.2 例题 CF1009F Dominant Indices
我们只关心每个节点子树内深度为
直接暴力显然会 T 飞。
这时候就要请出我们的 长链剖分优化DP 了。
我们可以采用一个优化策略:对于一个节点
这里我们详细说一下如何实现这些优化(好多博客都没有讲的很清楚,导致我看了很久,也可能是我太菜了)。
这里我们使用指针为
我们只需要对每一个长链的顶端节点申请内存,具体的,对
这样时间也能变成
答案的统计就很显然了。注意一点,由于枚举距离的时候没有枚举
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } int n, cur; int buf[N], ans[N], *f[N], mx[N]; int fa[N], d[N], mxd[N], son[N]; int tot, Head[N << 1], Next[N << 1], to[N << 1]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void dfs1(int x, int f){ d[x] = d[f] + 1, fa[x] = f; for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == f) continue; dfs1(y, x); if(mxd[y] > mxd[son[x]]) son[x] = y; } mxd[x] = mxd[son[x]] + 1; } void dfs2(int x){ f[x][0] = 1; if(son[x]){ f[son[x]] = f[x] + 1; //共享内存 dfs2(son[x]), ans[x] = ans[son[x]] + 1; //从长儿子继承答案 } for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa[x] || y == son[x]) continue; f[y] = buf + cur, cur += mxd[y], dfs2(y); //分配内存 for(int j = 1; j <= mxd[y]; ++j){ f[x][j] += f[y][j - 1]; //暴力合并 if(f[x][j] > f[x][ans[x]] || (f[x][j] == f[x][ans[x]] && j < ans[x])) ans[x] = j; //更新答案 } } if(f[x][ans[x]] == 1) ans[x] = 0; } int main(){ n = read(); for(int i = 1; i < n; ++i){ int u = read(), v = read(); add(u, v), add(v, u); } dfs1(1, 0), f[1] = buf, cur += mxd[1], dfs2(1); for(int i = 1; i <= n; ++i) printf("%d\n", ans[i]); return 0; }
3.3 经典结论
这个结论实在是太经典了,以至于它经常出现:选一个节点能覆盖它到根的所有节点。那么选
4.例题
Ⅰ. P4292 [WC2010]重建计划
Ⅱ. P5904 [POI2014]HOT-Hotels 加强版
Ⅲ.CF526G Spiders Evil Plan
Ⅳ. P3441 [POI2006]MET-Subway
找到直径一端作为根,长链剖分取
画一下图发现显然,这题居然卡空间,可以参考洛谷题解的另一种做法,我下面展示的代码会
点击查看代码
#include<bits/stdc++.h> using namespace std; const int N = 1e6 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } //bool x1; int n, k, rt, maxn; int tot, Head[N], to[N << 1], Next[N << 1]; int son[N], mxd[N]; vector<int> E[N]; bool vis[N]; void add(int u, int v){ to[++tot] = v, Next[tot] = Head[u], Head[u] = tot; } void find_root(int x){ queue<int> q; q.push(x); mxd[x] = 1; while(!q.empty()){ int x = q.front(); q.pop(); for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(mxd[y]) continue; mxd[y] = mxd[x] + 1; if(mxd[y] > maxn) maxn = mxd[y], rt = y; q.push(y); } } } void dfs(int x, int fa){ for(int i = Head[x]; i; i = Next[i]){ int y = to[i]; if(y == fa) continue; dfs(y, x); if(mxd[y] > mxd[son[x]]) son[x] = y; } mxd[x] = mxd[son[x]] + 1; } int main(){ n = read(), k = read() * 2 - 1; for(int i = 1; i < n; ++i){ int u = read(), v = read(); add(u, v), add(v, u); } find_root(1); memset(mxd, 0, sizeof(mxd)); dfs(rt, 0); int ans = 0; for(int i = 1; i <= n; ++i) E[mxd[i]].push_back(i); for(int i = n; i; --i){ for(int x : E[i]){ if(vis[x]) continue; int cur = x; --k; while(cur) ++ans, vis[cur] = 1, cur = son[cur]; if(!k) break; } if(!k) break; } printf("%d\n", ans); // bool x2; // fprintf(stderr, "%.3lf\n", abs(&x1 - &x2) / 1048576.0); return 0; }
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17455781.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步