树链剖分

本质上是将一个树变成多条链,转化成线段树,对树的操作转化成对线段树操作.

树链剖分详解(洛谷模板 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;
}
posted on 2022-08-23 10:12  naiji  阅读(16)  评论(0编辑  收藏  举报