树链剖分
本质上是将一个树变成多条链,转化成线段树,对树的操作转化成对线段树操作.
树链剖分详解(洛谷模板 P3384) - ChinHhh - 博客园 (cnblogs.com)
模板题:
P3384 【模板】轻重链剖分/树链剖分 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
ZJOI2008]树的统计 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
步骤
1,预处理:
dfs1()
- 标记每个点的深度dep[]
- 标记每个点的父亲fa[]
- 标记每个节点的子树大小(含它自己)
- 标记每个节点的重儿子编号son[]
dfs2()
- 标记每个点的新编号
- 赋值每个点的初始值到新编号上
- 处理每个点所在链的顶端
- 处理每条链
2.将对树的所有操作转化成对线段树的操作
1,修改任意两点间路径上的点权和(区间修改)
2,修改一节点及其子树的点权和(区间修改)
3,得到任意两个节点的点权和(区间查询)
4,得到一个节点及其子树的点权和(区间查询)
5,修改某个节点的权值(单点修改)
6,得到某个节点的权值(单点查询)
预处理:
int n, m, r;//n个点,n-1个边,m个操作,根节点为r
int power[N], newp[N], ans;
vector<int> a[N];
int dep[N], fa[N], siz[N], son[N], newid[N], top[N], cnt;
struct node
{
int left, right;
int sum, add;
};
node t[5 * N];
dfs1(r,r);dfs2(r,r);
void dfs1(int x, int father)
{
dep[x] = dep[father] + 1;
fa[x] = father;
siz[x] = 1;
int ma = 0;
for (int i = 0; i < a[x].size(); i++)
{
if (a[x][i] == father)
continue;
dfs1(a[x][i], x);
siz[x] += siz[a[x][i]];
if (ma < siz[a[x][i]])
ma = siz[a[x][i]], son[x] = a[x][i];
}
}
void dfs2(int x, int topx)
{
newid[x] = ++cnt;
newp[cnt] = power[x];
top[x] = topx;
if (son[x] == 0)
return;
dfs2(son[x], topx);
for (int i = 0; i < a[x].size(); i++)
{
if (a[x][i] == son[x] || a[x][i] == fa[x])
continue;
dfs2(a[x][i], a[x][i]);
}
}
建线段树
void push_down(int i)
{
if (t[i].add != 0)
{
t[i << 1].add += t[i].add;
t[i << 1 | 1].add += t[i].add;
t[i << 1].sum += t[i].add * (t[i << 1].right - t[i << 1].left + 1);
t[(i << 1) + 1].sum += t[i].add * (t[(i << 1) + 1].right - t[(i << 1) + 1].left + 1);
t[i].add = 0;
}
}
void build(int l, int r, int i)
{
t[i].left = l; t[i].right = r;
if (l == r)
{
t[i].sum = newp[l];
return;
}
int mid = (l + r) / 2;
build(l, mid, i << 1);
build(mid + 1, r, i << 1 | 1);
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
}
区间修改1(将两点及路径上的点权值,都增加z):
void f1(int x, int y, int z)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
swap(x, y);
update1(newid[top[x]], newid[x], 1, z);
x = fa[top[x]];
}
if (dep[x] > dep[y])
swap(x, y);
update1(newid[x], newid[y], 1, z);
}
void update1(int l, int r, int i, int k)
{
if (t[i].left == l && t[i].right == r)
{
t[i].sum = t[i].sum + k * (r - l + 1);
t[i].add = t[i].add + k;
return;
}
push_down(i);
int mid = (t[i].left + t[i].right) / 2;
if (r <= mid)
update1(l, r, i << 1, k);
else if (l > mid)
update1(l, r, i << 1 | 1, k);
else
{
update1(l, mid, i << 1, k);
update1(mid + 1, r, i << 1 | 1, k);
}
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
}
区间修改2(将点x及以x为根的子树的权值+z)
update(newid[x], newid[x] + siz[x] - 1, 1, z);
区间查询1(查询 两点及路径上的点权值和):
void f2(int x, int y)//查找点x,y及路径上的点权值和,答案给ans
{
ans = 0;
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
swap(x, y);
query2(newid[top[x]], newid[x], 1);
x = fa[top[x]];
}
if (dep[x] > dep[y])
swap(x, y);
query2(newid[x], newid[y], 1);
printf("%lld\n", ans);
}
void query2(int l, int r, int i)
{
if (t[i].left == l && t[i].right == r)
{
ans = ans + t[i].sum;
return;
}
push_down(i);
int mid = (t[i].left + t[i].right) / 2;
if (l > mid)
query2(l, r, i << 1 | 1);
else if (r <= mid)
query2(l, r, i << 1);
else
{
query2(l, mid, i << 1);
query2(mid + 1, r, i << 1 | 1);
}
}
区间查询2(查询点x及以x为根的子树的权值和)
ans = 0;
query2(newid[x], newid[x] + siz[x] - 1, 1);
单点修改:(将点x的权值改为y)
update2(newid[x], y, 1);//给update的应该是x对应的新点
void update2(int x, int y, int i)
{
if (t[i].left == t[i].right && t[i].left == x)
{
t[i].sum = y;
return;
}
int mid = (t[i].left + t[i].right) / 2;
if (x <= mid)
update2(x, y, i << 1);
else if (x > mid)
update2(x, y, i << 1 | 1);
t[i].sum = t[i << 1].sum + t[i << 1 | 1].sum;
}