题目中出现的树链剖分一般分为两种,重链剖分和长链剖分
重剖主要用于维护子树信息和链信息,长剖主要用于维护子树中只与深度有关的信息
从根开始对树进行深度优先搜索,同时优先搜索子树深度最深的儿子
优先搜索子树深度最深的儿子 (以下以重儿子表示) 使得 每条长链在 dfs 序上是连续的
重链剖分,还是根据 O ( l o g n ) O(logn) O(logn)条轻链的性质,如果k级组先就在当前重链上则直接找到,否则往上一条重链跳。复杂度 O ( l o g n ) O(logn) O(logn)
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; //长度最大的为重儿子
}
}
数组大小
数组大小只需要开 t m p [ m a x n ] tmp[maxn] tmp[maxn],即为所有长链的点的总数
状态数组 ∗ s u m [ m a x n ] *sum[maxn] ∗sum[maxn],指向 t m p tmp tmp上,长链所在区间
∗ i d *id ∗id用于维护长链区间的移动
数组维护
s u m [ i ] [ d ] sum[i][d] sum[i][d]表示节点 i i i的深度为 d d d的状态
重链的继承
s u m [ i ] [ j ] = s u m [ i ] [ j + 1 ] sum[i][j]=sum[i][j+1] sum[i][j]=sum[i][j+1]为某节点的深度x的状态可以直接继承其长链深度为x+1的
s u m [ i ] = s u m [ j ] + 1 sum[i]=sum[j]+1 sum[i]=sum[j]+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方程
}
}
}
给定一棵根为 1 的树, 对于每个节点,求子树 中,哪个距离下的节点 数量最多
当数量相同时,取较小 的那个距离值
我们对树进行长链剖分,记录 s u m [ x ] sum[x] sum[x]数组表 示节点x不同距离的点的数量
同一条长链中 s u m [ f x ] [ i + 1 ] = s u m [ x ] [ i ] sum[fx][i+1]=sum[x][i] sum[fx][i+1]=sum[x][i], 直接继承长链,暴力轻 儿子即可
给定一棵树,树的边长均为1,每个节点上标着一个字母
多次询问,每次给出一个v和一个d,要求查询v的子树 中深度为 d 的节点所标字母能否通过合理排列形成回文串
我们发现能组合成回文串的字母集包含奇数个字母最多只有一种
因此我们对字母集进行状压,1和0分别表示 该字母出现奇数次或者偶数次
a [ x ] [ i ] a[x][i] a[x][i] 表示 x 为子树,距离 x 深度为 i 的字母集
对树进行长链剖分,在同一条长链上有 a [ f x ] [ i + 1 ] = a [ x ] [ i ] a[fx][i+1]=a[x][i] a[fx][i+1]=a[x][i]长链继承
短链对应深度暴力 xor 即可询问需保存在点上,在对应的点统计答案
求一棵树上三点距离 两两相等的三元组数
设 f [ i ] [ j ] f[i][j] f[i][j]表示i子树距离 i 为 j 的点数量
设 g [ i ] [ j ] g[i][j] g[i][j] 表示 i 子树两点 l c a lca lca距离彼此为d,且 该 l c a lca lca距离i点为d-j的 点对数
#include
#include
#include
#include
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]);
}
#include
#include
#include
#include
#include
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");
}
#include
#include
#include
#include
#include
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);
}