树链剖分

树链剖分就是把树上的链分成重链和轻链,然后在重链和轻链上进行求和、修改,求最大值,最小值等等。。。。


树链剖分只是把找链这个环节优化到了log(n),具体的总的复杂度是多少,还要看你储存链的数据结构是什么。如果用的是线段树,那么复杂度大概是log(n) * log(n);如果用的是树套树,那么复杂度大概是log(n)*log(n)*log(n);如果是树链剖分加上树套树写区间第k大,那么就是(log(n))^4了。





树链剖分要用到以下一些数组来构造轻链重链:

记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度,top[v]表示v所在链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的儿子节点,w[v]表示v与父亲节点的连边在线段树中的位置。

以上这些数组可以用两个dfs全部求出来

还要定义以下这些名称:

重儿子:siz[u]为v的字节点中siz最大的,那么u就是v的重儿子。

轻儿子:除此之外的都是轻儿子。

重边:点v与重儿子的连边。

轻边:点v与轻儿子的连边。

重链:由重边组成的路径。

轻链:因为轻边不可能连续,所以轻链就是轻边。


树链剖分有两个性质:

性质1.如果(v,u)为轻边,那么siz[u] * 2 < siz[v]      ps:目前没发现这个性质对做题有什么作用。。,可能只是在证明log(n)的算法中有用

性质2.对于树上的任意两点间的路径,所经过的重链和轻链的个数都不超过log(n)。  这个性质保证了找路径的复杂度是log(n)


spoj:375

题意:修改某条边的权值,求x到y中边的最大值

代码

/*
   树链剖分,基于边
*/
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 20005;


int n;
struct ppp{
	int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v,int c){
	e[tole].u = u;e[tole].c = c;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}


int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号


void dfs1(int u,int pre,int depth){
	dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u])continue;
		dfs1(v,u,depth + 1);
		siz[u] += siz[v];
		if(siz[son[u]] < siz[v])//找到重儿子
			son[u] = v;
	}
}
void dfs2(int u,int pre){
	top[u] = pre;
	id[u] = ++cnt_node;//给当前边在线段树上编号
	if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u] || v == son[u])continue;
		dfs2(v,v);
	}
}//至此所有重链轻链以编号并求出
void init(){
	scanf("%d",&n);
	tole = cnt_node = 0;
	for(int i = 0;i <= n;i++){
		head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
	}
	for(int i = 1,a,b,c;i < n;i++){
		scanf("%d%d%d",&a,&b,&c);
		make_edge(a,b,c);
		make_edge(b,a,c);
	}
}
char ques[50];
int x,y;
struct pp{
	int l,r,mid,c;
	pp(){}
	void make(int _l,int _r){
		l = _l,r = _r,mid = (l + r) >> 1;
	}
}node[maxn * 4];
int ori[maxn];
void update(int o){
	node[o].c = max(node[o << 1].c,node[o << 1 | 1].c);
}
void build(int l,int r,int o){
	node[o].make(l,r);
	if(l == r){
		node[o].c = ori[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l,mid,o << 1);
	build(mid + 1,r,o << 1 | 1);
	update(o);
}
int query(int l,int r,int o){
	if(x <= l && r <= y){
		return node[o].c;
	}
	int &mid = node[o].mid;
	if(y <= mid)return query(l,mid,o << 1);
	else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
	else {
		return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
	}
}
void modify(int l,int r,int o){
	if(l == r && x == l){
		node[o].c = y;return;
	}
	int &mid = node[o].mid;
	if(x <= mid)modify(l,mid,o << 1);
	else modify(mid + 1,r,o << 1 | 1);
	update(o);
}
int go(int u,int v){
	int f1 = top[u],f2 = top[v];
	int ans = -1;
	while(f1 != f2){
		if(dep[f1] < dep[f2]){
			swap(f1,f2);
			swap(u,v);
		}
		x = id[f1],y = id[u];
		ans = max(ans,query(1,cnt_node,1));
		u = fa[f1];
		f1 = top[u];
	}
	if(u == v)return ans;
	if(dep[u] > dep[v])swap(u,v);
	x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
	//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
	ans = max(ans,query(1,cnt_node,1));
	return ans;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		init();
		dfs1(1,0,0);
		dfs2(1,0);
		for(int i = 1;i < n;i++){
			x = i - 1;
			x = x << 1;
			if(dep[e[x].u] < dep[e[x].v])swap(e[x].u,e[x].v);
			ori[id[e[x].u]] = e[x].c;//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
		}
		build(1,cnt_node,1);
		int a,b;
		while(true){
			scanf(" %s",ques);
			if(ques[0] == 'D')break;
			if(ques[0] == 'Q'){
				scanf("%d%d",&a,&b);
				printf("%d\n",go(a,b));
			}else{
				scanf("%d%d",&a,&y);
				x = id[e[(a - 1) * 2].u];
				modify(1,n,1);
			}
		}
	}
}




上面这个树链剖分是基于边来建立树上的点的,具体来说,就是把每条边(u->v)的权值转化为v代表这条边的权值,由此可见,这棵树的根是没有权值的。所以在上面的代码的查询片段(go()函数中)中会出现。

x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
所以这里是son[u]而不是直接是u。

出现这样的情况是因为我们强制把边的权值加到了点上(方便加入线段树),如果题目本来就是在点上+,-某个值,查询两点之间点上的权值关系的话,就不用这样考虑了。那么其实上面的这段代码也可以去掉

	if(u == v)return ans;
直接都当成同样的考虑,保存下面的代码就可以了。



如果是点修改的话,可以用如下的代码作参考:


/*
   树链剖分,基于点
*/
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 40005;


int n;
struct ppp{
	int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v){
	e[tole].u = u;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}
int zhi[maxn];

int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号


void dfs1(int u,int pre,int depth){
	dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u])continue;
		dfs1(v,u,depth + 1);
		siz[u] += siz[v];
		if(siz[son[u]] < siz[v])//找到重儿子
			son[u] = v;
	}
}
void dfs2(int u,int pre){
	top[u] = pre;
	id[u] = ++cnt_node;//给当前边在线段树上编号
	if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u] || v == son[u])continue;
		dfs2(v,v);
	}
}//至此所有重链轻链以编号并求出
void init(){
	tole = cnt_node = 0;
	for(int i = 0;i <= n;i++){
		head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
	}
	for(int i = 1,a,b;i < n;i++){
		scanf("%d%d",&a,&b);
		make_edge(a,b);
		make_edge(b,a);
	}
	for(int i = 1;i <= n;i++)scanf("%d",zhi + i);
}
char ques[50];
int x,y;
struct pp{
	int l,r,mid,c,maxx;
	pp(){}
	void make(int _l,int _r){
		l = _l,r = _r,mid = (l + r) >> 1;
		maxx = -99999999;c = 0;
	}
}node[maxn * 4];
int ori[maxn];
void update(int o){
	node[o].maxx = max(node[o << 1].maxx,node[o << 1 | 1].maxx);
	node[o].c = node[o << 1].c + node[o << 1 | 1].c;
}
void build(int l,int r,int o){
	node[o].make(l,r);
	if(l == r){
		node[o].c = ori[l];
		node[o].maxx = ori[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l,mid,o << 1);
	build(mid + 1,r,o << 1 | 1);
	update(o);
}
int query(int l,int r,int o){
	if(x <= l && r <= y){
		return node[o].maxx;
	}
	int &mid = node[o].mid;
	if(y <= mid)return query(l,mid,o << 1);
	else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
	else {
		return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
	}
}

int query1(int l,int r,int o){
	if(x <= l && r <= y){
		return node[o].c;
	}
	int &mid = node[o].mid;
	if(y <= mid)return query1(l,mid ,o << 1);
	else if(x > mid)return query1(mid + 1,r,o << 1 | 1);
	else return query1(l,mid,o << 1) + query1(mid + 1,r,o << 1 | 1);
}
void modify(int l,int r,int o){
	if(l == r && x == l){
		node[o].c = y;
		node[o].maxx = y;
		return;
	}
	int &mid = node[o].mid;
	if(x <= mid)modify(l,mid,o << 1);
	else modify(mid + 1,r,o << 1 | 1);
	update(o);
}
int go(int u,int v,int flag){
	int f1 = top[u],f2 = top[v];
	int ans = 0;
	if(flag)ans = -999999999;
	while(f1 != f2){
		if(dep[f1] < dep[f2]){
			swap(f1,f2);
			swap(u,v);
		}
		x = id[f1],y = id[u];
		if(flag == 1)
		ans = max(ans,query(1,cnt_node,1));
		else ans += query1(1,cnt_node,1);
		u = fa[f1];
		f1 = top[u];
	}
	if(dep[u] > dep[v])swap(u,v);
	x = id[u],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
	//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
	if(flag)
	ans = max(ans,query(1,cnt_node,1));
	else ans += query1(1,cnt_node,1);
	return ans;
}
int main(){
	while(~scanf("%d",&n)){
		init();
		dfs1(1,0,0);
		dfs2(1,0);
		for(int i = 1;i <= n;i++){
			ori[id[i]] = zhi[i];//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
		}
		build(1,cnt_node,1);
		int a,b;
		int q;
		scanf("%d",&q);
		while(q--){
			scanf(" %s",ques);
			if(ques[0] == 'Q'){
				scanf("%d%d",&a,&b);
				if(ques[1] == 'S'){
					printf("%d\n",go(a,b,0));
				}else {
					printf("%d\n",go(a,b,1));
				}
			}else{
				scanf("%d%d",&a,&y);
				zhi[a] = y;
				x = id[a];
				modify(1,cnt_node,1);
			}
		}
	}
}




再贴一下poj3237的代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <string>
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 20005;
const int INF = 999999999;
int n;
struct ppp{
	int u,v,nex,c;
}e[maxn * 4];
int tole,head[maxn];
void make_edge(int u,int v,int c){
	e[tole].u = u;e[tole].c = c;e[tole].v = v;e[tole].nex = head[u];head[u] = tole++;
}


int siz[maxn],fa[maxn],dep[maxn],son[maxn],id[maxn],top[maxn];
int cnt_node;//这个是边在线段树中的编号


void dfs1(int u,int pre,int depth){
	dep[u] = depth;siz[u] = 1;son[u] = 0;fa[u] = pre;
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u])continue;
		dfs1(v,u,depth + 1);
		siz[u] += siz[v];
		if(siz[son[u]] < siz[v])//找到重儿子
			son[u] = v;
	}
}
void dfs2(int u,int pre){
	top[u] = pre;
	id[u] = ++cnt_node;//给当前边在线段树上编号
	if(son[u])dfs2(son[u],pre);//不是叶子节点的时候往重链走
	for(int i = head[u];~i;i = e[i].nex){
		int v = e[i].v;
		if(v == fa[u] || v == son[u])continue;
		dfs2(v,v);
	}
}//至此所有重链轻链以编号并求出
void init(){
	scanf("%d",&n);
	tole = cnt_node = 0;
	for(int i = 0;i <= n;i++){
		head[i] = -1;siz[i] = dep[i] = son[i] = id[i] = top[i] = 0;
	}
	for(int i = 1,a,b,c;i < n;i++){
		scanf("%d%d%d",&a,&b,&c);
		make_edge(a,b,c);
		make_edge(b,a,c);
	}
}
char ques[50];
int x,y;
struct pp{
	int l,r,mid,c,f,maxx,minn;
	pp(){}
	void make(int _l,int _r){
		l = _l,r = _r,mid = (l + r) >> 1;f = 0;maxx = -INF;minn = INF;
	}
}node[maxn * 4];
int ori[maxn];
void update(int o){
	node[o].maxx = max(node[o << 1].maxx,node[o << 1 | 1].maxx);
	node[o].minn = min(node[o << 1].minn,node[o << 1 | 1].minn);
}
void down(int o){
	if(node[o].f){
		node[o].f = 0;
		node[o << 1].f = node[o << 1].f ^ 1;
		node[o << 1 | 1].f = node[o << 1 | 1].f ^ 1;
		swap(node[o << 1].maxx,node[o << 1].minn);
		node[o << 1].maxx = -node[o << 1].maxx;
		node[o << 1].minn = -node[o << 1].minn;
		swap(node[o << 1 | 1].maxx,node[o << 1 | 1].minn);
		node[o << 1 | 1].maxx = -node[o << 1 | 1].maxx;
		node[o << 1 | 1].minn = -node[o << 1 | 1].minn;
	}
}
void build(int l,int r,int o){
	node[o].make(l,r);
	if(l == r){
		node[o].c = ori[l];
		node[o].maxx = ori[l];
		node[o].minn = ori[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(l,mid,o << 1);
	build(mid + 1,r,o << 1 | 1);
	update(o);
}
int query(int l,int r,int o){
	if(x <= l && r <= y){
		return node[o].maxx;
	}
	int &mid = node[o].mid;
	down(o);
	if(y <= mid)return query(l,mid,o << 1);
	else if(x > mid)return query(mid + 1,r ,o << 1 | 1);
	else {
		return max(query(l,mid,o << 1),query(mid + 1,r,o << 1 | 1));
	}
}
void chage(int l,int r,int o){
	if(x <= l && r <= y){
		node[o].f ^= 1;
		swap(node[o].maxx,node[o].minn);
		node[o].maxx = -node[o].maxx;
		node[o].minn = -node[o].minn;
		return;
	}
	down(o);
	int &mid = node[o].mid;
	if(y <= mid)chage(l,mid,o << 1);
	else if(x > mid)chage(mid + 1,r,o << 1 | 1);
	else{
		chage(l,mid,o << 1);
		chage(mid + 1,r,o << 1 | 1);
	}
	update(o);
}
void modify(int l,int r,int o){
	if(l == r && x == l){
		node[o].c = y;
		node[o].maxx = y;
		node[o].minn = y;
		return;
	}
	int &mid = node[o].mid;
	down(o);
	if(x <= mid)modify(l,mid,o << 1);
	else modify(mid + 1,r,o << 1 | 1);
	update(o);
}
int go(int u,int v,int flag){
	int f1 = top[u],f2 = top[v];
	int ans = -999999999;
	while(f1 != f2){
		if(dep[f1] < dep[f2]){
			swap(f1,f2);
			swap(u,v);
		}
		x = id[f1],y = id[u];
		if(!flag)
		ans = max(ans,query(1,cnt_node,1));
		else chage(1,cnt_node,1);
		u = fa[f1];
		f1 = top[u];
	}
	if(u == v)return ans;
	if(dep[u] > dep[v])swap(u,v);
	x = id[son[u]],y = id[v];//注意这里是son[u],这里每个点的边值相当于这个点和父亲的边的值,
	//在这里当f1 == f2,且u != v时会发现u,v必定在同一条重链(轻链)中,那么应该是u的son的id才是线段树的左区间点
	if(!flag)
	ans = max(ans,query(1,cnt_node,1));
	else chage(1,cnt_node,1);
	return ans;
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		init();
		dfs1(1,0,0);
		dfs2(1,0);
		for(int i = 1;i < n;i++){
			x = i - 1;
			x = x << 1;
			if(dep[e[x].u] < dep[e[x].v])swap(e[x].u,e[x].v);
			ori[id[e[x].u]] = e[x].c;//这里将边权值强制赋值在点上,以点的深度更深的保存这条边的权值,这样利于在线段树上操作
		}
		build(1,cnt_node,1);
		int a,b;
		while(true){
			scanf(" %s",ques);
			if(ques[0] == 'D')break;
			if(ques[0] == 'Q'){
				scanf("%d%d",&a,&b);
				printf("%d\n",go(a,b,0));
			}else if(ques[0] == 'C'){
				scanf("%d%d",&a,&y);
				x = id[e[(a - 1) * 2].u];
				modify(1,n,1);
			}else {
				scanf("%d%d",&a,&b);
				go(a,b,1);
			}
		}
	}
}













  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值