树链剖分
题目中出现的树链剖分一般分为两种,重链剖分和长链剖分
- 重链剖分:选择子树最大的儿子, 将其归入当前点所在 的同一条重链
- 长链剖分:选择向下能达到的深 度最深的儿子,将其归 入当前点所在的同一 条长链
重剖主要用于维护子树信息和链信息,长剖主要用于维护子树中只与深度有关的信息
长剖
从根开始对树进行深度优先搜索,同时优先搜索子树深度最深的儿子
优先搜索子树深度最深的儿子 (以下以重儿子表示) 使得 每条长链在 dfs 序上是连续的
- 树被切分为多条长链
- 一条长链顶端点的父亲节点所在长链一定长于这条长链,一个点 k 级祖先所在长链一定长于 k
- 任一点到根最多经过 条长链,所有长链 总长度为
- 能够在线性时间维护 子树中只与深度有关 的信息
k级祖先
重剖
重链剖分,还是根据条轻链的性质,如果k级组先就在当前重链上则直接找到,否则往上一条重链跳。复杂度
长剖
- 求祖先 ST 表
- O(n) 求出每个长链顶端 1 到 len(x) 级祖先和重儿子
- O(n) 预处理每个数字最高位 1 的位数 b[i]
- 对于 k 级祖先,我们先求 ST 表 2b[k] 级祖先
- 其长链信息一定大于 k−2b[k],直接 O(1) 查表即可
详解链接
长链剖分优化树上DP
O(n)统计每个点子树中以深度为下标的可合并信息
长链剖分
我们首先要预处理以下内容
- 节点的深度
- 节点的重儿子(子树深度最深的儿子)
- 长链的长度
int deep[maxn], h_size[maxn], son[maxn];
//deep记录深度,h_size记录重链长度,son记录重儿子
void dfs1(int now, int f) {
deep[now] = deep[f] + 1;
h_size[now] = deep[now];
for (int i = head[now]; i; i = edge[i].next) {
if (f == edge[i].v)continue;
dfs1(edge[i].v, now);
h_size[now] = max(h_size[now], h_size[edge[i].v]);
if (h_size[edge[i].v] > h_size[son[now]])
son[now] = edge[i].v; //长度最大的为重儿子
}
}
树上DP
-
数组大小
数组大小只需要开,即为所有长链的点的总数
状态数组,指向上,长链所在区间
用于维护长链区间的移动 -
数组维护
表示节点的深度为的状态 -
重链的继承
为某节点的深度x的状态可以直接继承其长链深度为x+1的
-
轻链的合并
直接将轻链合并到重链即可
由于每个点只会合并一次,复杂度为O(n)
特别注意,这样实现也有相应的缺点,即若动规的数组高于一维,那么数组是一次性的,不能在完成转移后查询中间过程的值。
原因是部分内存被共用掉了
int* sum[maxn], tmp[maxn], * id = tmp;
//sum为数组指针,数组总大小为maxn(所有长链加起来为n个点),id为指针
void dfs(int now, int f) {
sum[now][0] = 1;
if (son[now]) {
sum[son[now]] = sum[now] + 1; //继承其长链,sum[fx][i+1]=sum[x][i]
dfs(son[now], now);
}
for (int i = head[now]; i; i = edge[i].next) {
if (edge[i].v == f || edge[i].v == son[now])continue;
sum[edge[i].v] = id; //数组开始点
id += h_size[edge[i].v] - deep[edge[i].v] + 1; //开数组长度为长链长度
dfs(edge[i].v, now);
for (int j = 1; j <= h_size[edge[i].v] - deep[edge[i].v] + 1; j++) {
//DP() ,dp方程
}
}
}
CF1009F Dominant Indices
题意
给定一棵根为 1 的树, 对于每个节点,求子树 中,哪个距离下的节点 数量最多
当数量相同时,取较小 的那个距离值
思路
我们对树进行长链剖分,记录数组表 示节点x不同距离的点的数量
同一条长链中, 直接继承长链,暴力轻 儿子即可
CF570D Tree Requests
题意:
给定一棵树,树的边长均为1,每个节点上标着一个字母
多次询问,每次给出一个v和一个d,要求查询v的子树 中深度为 d 的节点所标字母能否通过合理排列形成回文串
思路:
我们发现能组合成回文串的字母集包含奇数个字母最多只有一种
因此我们对字母集进行状压,1和0分别表示 该字母出现奇数次或者偶数次
表示 x 为子树,距离 x 深度为 i 的字母集
对树进行长链剖分,在同一条长链上有 长链继承
短链对应深度暴力 xor 即可询问需保存在点上,在对应的点统计答案
BZOJ4543 Hotel加强版
题意
求一棵树上三点距离 两两相等的三元组数
思路
设表示i子树距离 i 为 j 的点数量
设 表示 i 子树两点距离彼此为d,且 该距离i点为d-j的 点对数
我们发现状态转移只跟节点深度有关,因此可以长链剖 分优化
同一条长链上对于 f 数组有 ,对于g数组有 ,
继承重儿子的 g 和 f 函数, 暴力统计轻链,同时计算 ans 即可
例题代码
Dominant Indices
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#pragma warning (disable:4996)
typedef long long LL;
const int maxn = 1000005;
const int maxm = 1000005;
int n, res[maxn];
int head[maxn], tot;
struct Edge
{
int v;
int next;
}edge[maxm << 1];
void init() {
memset(head, 0, sizeof(head));
tot = 0;
}
inline void AddEdge(int u, int v) {
edge[++tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
int deep[maxn], h_size[maxn], son[maxn];
//deep记录深度,h_size记录重链长度,son记录重儿子
void dfs1(int now, int f) {
deep[now] = deep[f] + 1;
h_size[now] = deep[now];
for (int i = head[now]; i; i = edge[i].next) {
if (f == edge[i].v)continue;
dfs1(edge[i].v, now);
h_size[now] = max(h_size[now], h_size[edge[i].v]);
if (h_size[edge[i].v] > h_size[son[now]])
son[now] = edge[i].v; //长度最大的为重儿子
}
}
int* sum[maxn], tmp[maxn], * id = tmp;
//sum为数组指针,数组总大小为maxn(所有长链加起来为n个点),id为指针
void dfs(int now, int f) {
sum[now][0] = 1;
if (son[now]) {
sum[son[now]] = sum[now] + 1; //继承其长链,sum[fx][i+1]=sum[x][i]
dfs(son[now], now);
res[now] = res[son[now]] + 1; //同上,继承
}
for (int i = head[now]; i; i = edge[i].next) {
if (edge[i].v == f || edge[i].v == son[now])continue;
sum[edge[i].v] = id; //数组开始点
id += h_size[edge[i].v] - deep[edge[i].v] + 1; //开数组长度为长链长度
dfs(edge[i].v, now);
for (int j = 1; j <= h_size[edge[i].v] - deep[edge[i].v] + 1; j++) {
sum[now][j] += sum[edge[i].v][j - 1]; //对于轻链dp
if (sum[now][j] > sum[now][res[now]] || sum[now][j] == sum[now][res[now]] && j < res[now])
res[now] = j;
}
}
if (sum[now][res[now]] == 1)res[now] = 0; //特判,若为1个,那么0是最小的
}
int main() {
int n; scanf("%d", &n);
int u, v;
for (int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
}
dfs1(1, 0); //长链剖分
sum[1] = id; id += h_size[1];
dfs(1, 0); //树上dp
for (int i = 1; i <= n; i++)
printf("%d\n", res[i]);
}
Tree Requests
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#pragma warning (disable:4996)
typedef pair<int, int> pii;
typedef long long LL;
const int maxn = 500005;
const int maxm = 500005;
int n, m, x;
bool res[maxn];
int head[maxn], tot;
struct Edge
{
int v;
int next;
}edge[maxm << 1];
void init() {
memset(head, 0, sizeof(head));
tot = 0;
}
inline void AddEdge(int u, int v) {
edge[++tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
int deep[maxn], h_size[maxn], son[maxn];
//deep记录深度,h_size记录重链长度,son记录重儿子
void dfs1(int now, int f) {
deep[now] = deep[f] + 1;
h_size[now] = deep[now];
for (int i = head[now]; i; i = edge[i].next) {
if (f == edge[i].v)continue;
dfs1(edge[i].v, now);
h_size[now] = max(h_size[now], h_size[edge[i].v]);
if (h_size[edge[i].v] > h_size[son[now]])
son[now] = edge[i].v; //长度最大的为重儿子
}
}
int w[maxn];
char s[maxn];
vector<pii> E[maxn];
int* sum[maxn], tmp[maxn], * id = tmp;
void dfs(int now, int f) {
sum[now][0] = w[now];
if (son[now]) {
sum[son[now]] = sum[now] + 1;
dfs(son[now], now);
}
for (int i = head[now]; i; i = edge[i].next) {
if (edge[i].v == f || edge[i].v == son[now])continue;
sum[edge[i].v] = id;
id += h_size[edge[i].v] - deep[edge[i].v] + 1;
dfs(edge[i].v, now);
for (int j = 1; j <= h_size[edge[i].v] - deep[edge[i].v] + 1; j++)
sum[now][j] ^= sum[edge[i].v][j - 1];
}
int depth = deep[now], son_depth;
for (int i = 0; i < E[now].size(); i++) {
son_depth = E[now][i].first - depth;
if (son_depth <= 0 || E[now][i].first > h_size[now]) {
res[E[now][i].second] = true;
continue;
}
if (sum[now][son_depth] == 0) {
res[E[now][i].second] = true;
continue;
}
int cnt = 0, j;
for (j = 0; j < 26; j++) {
if (sum[now][son_depth] & (1 << j))
cnt++;
if (cnt == 2)break;
}
if (j < 26)res[E[now][i].second] = false;
else res[E[now][i].second] = true;
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; i++) {
scanf("%d", &x);
AddEdge(i, x);
AddEdge(x, i);
}
scanf("%s", s + 1);
for (int i = 1; i <= n; i++) {
w[i] = 1 << (s[i] - 'a');
}
int d;
for (int i = 1; i <= m; i++) {
scanf("%d%d", &x, &d);
E[x].push_back(pii(d, i));
}
dfs1(1, 0);
sum[1] = id; id += h_size[1];
dfs(1, 0);
for (int i = 1; i <= m; i++)
if (res[i])printf("Yes\n");
else printf("No\n");
}
Hotel加强版
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#pragma warning (disable:4996)
typedef pair<int, int> pii;
typedef long long LL;
const int maxn = 100005;
const int maxm = 100005;
int n;
int head[maxn], tot;
struct Edge
{
int v;
int next;
}edge[maxm << 1];
void init() {
memset(head, 0, sizeof(head));
tot = 0;
}
inline void AddEdge(int u, int v) {
edge[++tot].v = v;
edge[tot].next = head[u];
head[u] = tot;
}
int deep[maxn], h_size[maxn], son[maxn];
//deep记录深度,h_size记录重链长度,son记录重儿子
void dfs1(int now, int f) {
deep[now] = deep[f] + 1;
h_size[now] = deep[now];
for (int i = head[now]; i; i = edge[i].next) {
if (f == edge[i].v)continue;
dfs1(edge[i].v, now);
h_size[now] = max(h_size[now], h_size[edge[i].v]);
if (h_size[edge[i].v] > h_size[son[now]])
son[now] = edge[i].v; //长度最大的为重儿子
}
}
LL* f[maxn], * g[maxn], tmp[maxn << 2], * id = tmp;
LL ans;
void dfs(int now, int fa) {
if (son[now]) {
f[son[now]] = f[now] + 1;
g[son[now]] = g[now] - 1;
dfs(son[now], now);
}
f[now][0] = 1;
ans += g[now][0];
for (int i = head[now]; i; i = edge[i].next) {
if (edge[i].v == fa || edge[i].v == son[now])continue;
f[edge[i].v] = id; id += ((h_size[edge[i].v] - deep[edge[i].v]) << 1) + 2;
g[edge[i].v] = id; id += (h_size[edge[i].v] - deep[edge[i].v]) + 1;
dfs(edge[i].v, now);
for (int j = 0; j <= h_size[edge[i].v] - deep[edge[i].v]; j++) {
if (j)ans += g[edge[i].v][j] * f[now][j - 1]; //长链上距离(j-1)的点数量
ans += f[edge[i].v][j] * g[now][j + 1]; //短链上的点*长链上的点对
}
for (int j = 0; j <= h_size[edge[i].v] - deep[edge[i].v]; j++) {
g[now][j + 1] += f[edge[i].v][j] * f[now][j + 1]; //短链一个点+长链一个点的合并
if (j)g[now][j - 1] += g[edge[i].v][j]; //短链点对加入
f[now][j + 1] += f[edge[i].v][j];
}
}
}
int main() {
scanf("%d", &n);
int u, v;
for (int i = 2; i <= n; i++) {
scanf("%d%d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
}
dfs1(1, 0);
f[1] = id; id += (h_size[1] << 1) + 2; //给g数组预留向前移动的空间
g[1] = id; id += h_size[1] + 1;
dfs(1, 0);
printf("%lld\n", ans);
}