树链剖分(bzoj 1036: [ZJOI2008]树的统计Count)

树链剖分:

把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一条链,复杂度

为O(logn),总体复杂度O(nlog²n)


步骤:

①将树的边分成重边和轻边,令siz[u]为u节点所有子节点的个数,v是u所有儿子中siz[]值最大的,

那么边(u,v)就是重边,否则就是轻边

深搜一次就好,顺便求出每个节点的深度和每个节点的父亲是谁

数组:在第一次深搜时全部搜出

fa[u]      ------      u节点的父亲(根的父亲为0)

siz[u]      ------      u节点所有子节点的个数(叶子节点的siz[]值显然为0)

dep[u]      ------      u节点的深度(根的深度为1)

son[u]      ------      u节点的重儿子 [边(u,son[u])为重边]


②第二次深搜,将树分解成链,[重边->重链,轻边->将两条(个)重链或叶子连接在一起],

并求出每个节点所在链的顶端节点

深搜顺序:如果当前节点是重链上的节点,则随着这条重链往下搜,搜完回溯后再搜索与这个点相连的其他轻儿子

最后每个节点对应线段(链)中的编号就是搜索的dtime

数组:在第二次搜索时全部搜出

top[u]      ------      u点所在链的顶端节点

rak[u]      ------     树中u节点剖分成链后对应的新编号

id[x]      ------      链中编号x的点对应的树的节点(反rak[])

经过前两次操作(两次搜索),树的剖分就完成了,树的问题就可以转化成链的问题


如上图:红色的边是重边,蓝色的边是轻边,红色的数字对应着当前节点在链中的编号,红点表示每个链的链头

性质:轻边(u,v)中,size(v)<=size(u/2)

从根到某一点的路径上,不超过logn条轻边和不超过logn条重边

对于每条重链,从链头到链尾编号从小到大



③:附录,例如这道题,如何修改某个节点u的值呢?

直接线段树单点更新,更新rak[u]点的值即可

如果查询两个节点u和v之间所有节点的和?

如果u和v在同一条重链中,那么好办,直接线段树区间查询一次即可,如果不在一条链中

就要让u点去找v点,不停地执行以下操作,让u点和v在同一条重链上,之后执行上个步骤

①u点爬到它所在重链的顶端节点,求出爬过的这段链的所有节点权值之和

②从该顶端节点爬向它的父亲(跑到另一条重链上了),之和继续执行步骤①

注意,如果每次u都是往上爬,深度是越来越低的!假设低过了v点的深度!那么u和v永远不可能碰面了,

所以每次爬的时候倒要比较下u和v的深度,让深度低的那个爬!!这也是为什么要求每个节点的深度

可能很难讲清楚,直接看代码好懂点

专门拉出来

int TreQuerys(int x, int y)		/*步骤③,模拟u和v相遇的过程,不停地线段树查询走过节点的权值和*/
{
	int sum, p1, p2;
	p1 = top[x], p2 = top[y], sum = 0;
	while(p1!=p2)
	{
		if(dep[p1]<dep[p2])			/*让深度低的那个往上爬*/
			swap(p1, p2), swap(x, y);
		sum += Querys(1, n, 1, rak[p1], rak[x]);	/*普通线段树查询,下同*/
		x = fa[p1], p1 = top[x];		/*我要爬了*/
	}
	if(dep[x]>dep[y])
		swap(x, y);
	sum += Querys(1, n, 1, rak[x], rak[y]);
	return sum;
}


1036: [ZJOI2008]树的统计Count

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 16866   Solved: 6868
[ Submit][ Status][ Discuss]

Description

  一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。我们将以下面的形式来要求你对这棵树完成一些操作: I. CHANGE u t : 把结点u的权值改为t II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值 III. QSUM u v: 询问从点u到点v的路径上的节点的权值和 注意:从点u到点v的路径上的节点包括u和v本身

Input

  输入的第一行为一个整数n,表示节点的个数。接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。接下来n行,每行一个整数,第i行的整数wi表示节点i的权值。接下来1行,为一个整数q,表示操作的总数。接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。 
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。

Output

  对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。

Sample Input

4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4

Sample Output

4
1
2
2
10
6
5
6
5
16


AC代码

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
using namespace std;
#define inf 2147483647;
vector<int> G[30005];
typedef struct
{
	int sum;
	int max;
}Tree;
Tree tre[125005];
int n, val[30005], son[30005], fa[30005], siz[30005], dep[30005];
int k, top[30005], rak[30005], id[30005];
void Sechs(int u, int p)		/*步骤①*/
{
	int v, i;
	fa[u] = p;
	dep[u] = dep[p]+1;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==p)
			continue;
		Sechs(v, u);
		siz[u] += siz[v]+1;
		if(son[u]==0 || siz[v]>siz[son[u]])
			son[u] = v;
	}
}

void Sechr(int u, int p)		/*步骤②*/
{
	int v, i;
	top[u] = p;
	rak[u] = ++k, id[k] = u;
	if(son[u]==0)
		return;
	Sechr(son[u], p);
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==son[u] || v==fa[u])
			continue;
		Sechr(v, v);
	}
}

void Create(int l, int r, int x);							/*四个线段树的简单操作*/
void Update(int l, int r, int x, int a, int b);
int Querys(int l, int r, int x, int a, int b);
int Queryx(int l, int r, int x, int a, int b);
int TreQuerys(int x, int y)		/*步骤③,模拟u和v相遇的过程,不停地线段树查询走过节点的权值和*/
{
	int sum, p1, p2;
	p1 = top[x], p2 = top[y], sum = 0;
	while(p1!=p2)
	{
		if(dep[p1]<dep[p2])
			swap(p1, p2), swap(x, y);
		sum += Querys(1, n, 1, rak[p1], rak[x]);
		x = fa[p1], p1 = top[x];
	}
	if(dep[x]>dep[y])
		swap(x, y);
	sum += Querys(1, n, 1, rak[x], rak[y]);
	return sum;
}

int TreQueryx(int x, int y)
{
	int now, p1, p2;
	p1 = top[x], p2 = top[y], now = -inf;
	while(p1!=p2)
	{
		if(dep[p1]<dep[p2])
			swap(p1, p2), swap(x, y);
		now = max(now, Queryx(1, n, 1, rak[p1], rak[x]));
		x = fa[p1], p1 = top[x];
	}
	if(dep[x]>dep[y])
		swap(x, y);
	now = max(now, Queryx(1, n, 1, rak[x], rak[y]));
	return now;
}

int main(void)
{
	int i, x, y, q;
	char str[15];
	while(scanf("%d", &n)!=EOF)
	{
		for(i=1;i<=n;i++)
			G[i].clear();
		for(i=1;i<=n-1;i++)
		{
			scanf("%d%d", &x, &y);
			G[x].push_back(y);
			G[y].push_back(x);
		}
		for(i=1;i<=n;i++)
			scanf("%d", &val[i]);
		memset(siz, 0, sizeof(siz));
		memset(son, 0, sizeof(son));
		k = 0;
		Sechs(1, 0);
		Sechr(1, 1);
		for(i=1;i<=n;i++)
			printf("%d ", rak[i]);
		scanf("%d", &q);
		Create(1, n, 1);
		while(q--)
		{
			scanf("%s%d%d", str+1, &x, &y);
			if(str[1]=='C')
				Update(1, n, 1, rak[x], y);
			else if(str[2]=='M')
				printf("%d\n", TreQueryx(x, y));
			else
				printf("%d\n", TreQuerys(x, y));
		}
	}
	return 0;
}

void Create(int l, int r, int x)
{
	int m;
	if(l==r)
	{
		tre[x].sum = tre[x].max = val[id[l]];
		return;
	}
	m = (l+r)/2;
	Create(l, m, x*2);
	Create(m+1, r, x*2+1);
	tre[x].sum = tre[x*2].sum+tre[x*2+1].sum;
	tre[x].max = max(tre[x*2].max, tre[x*2+1].max);
}

void Update(int l, int r, int x, int a, int b)
{
	int m;
	if(l==r && r==a)
	{
		tre[x].max = tre[x].sum = b;
		return;
	}
	m = (l+r)/2;
	if(a<=m)  Update(l, m, x*2, a, b);
	else  Update(m+1, r, x*2+1, a, b);
	tre[x].sum = tre[x*2].sum+tre[x*2+1].sum;
	tre[x].max = max(tre[x*2].max, tre[x*2+1].max);
}

int Querys(int l, int r, int x, int a, int b)
{
	int m, sum;
	if(l>=a && r<=b)
		return tre[x].sum;
	m = (l+r)/2;
	sum = 0;
	if(a<=m)  sum += Querys(l, m, x*2, a, b);
	if(b>=m+1)  sum += Querys(m+1, r, x*2+1, a, b);
	return sum;
}

int Queryx(int l, int r, int x, int a, int b)
{
	int m, now;
	if(l>=a && r<=b)
		return tre[x].max;
	m = (l+r)/2;
	now = -inf;
	if(a<=m)  now = max(now, Queryx(l, m, x*2, a, b));
	if(b>=m+1)  now = max(now, Queryx(m+1, r, x*2+1, a, b));
	return now;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值