登封造极之树——树链剖分

解决问题:给出一个树,有两个操作:1.修改某条树边的权值。    2.求某个节点到另一个的路径中的最大边权值。


基本原理:如果不是一棵树,而是一条链,那么很清楚用线段树做。而现在就是一棵树,那么我们就可以将树分成一条一条的链。


算法步骤:

1.构造:我们称这些链为重路径,每两条重路径之间由一条轻边连接,对于一棵树,每一个父节点u有且仅有一条重路连向一个子节点v,这个子节点v必须满足size[v]>=size[v`](v`指u的其他子节点)。通过dfs,我们就可以剖分整棵树了。

2.修改:找到点所在的边所在的重路径,用线段树的点修改可以很好的完成。

3.查找:分析两个点a和b,因为我们重路径的最大值可以用线段树的最大值查找,十分方便的求出来,我们就可以类比LCA的思路,比较a,b所在的重路径的顶端的深度,较深的点向上爬至顶部,循环,最后a和b要么是同一个点,算法结束,要么在同一个重路径中,最后调用一次线段树的最大值查找。


算法复杂度:O(T+q*(log n)^2)


参考程序:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define infi 0x7fffffff
#define maxn 10100
using namespace std;
struct edge{
	int loc,code,next;//loc边对应点的编号,code边的编号
};
struct TreeNode{
	int l,r,max;
	int lch,rch;
};
int tree[maxn],nodeCnt;
TreeNode node[maxn*10];
edge e[maxn*2];
int edgeCnt,a[maxn];
int father[maxn],//每个点的父节点
    path_top[maxn],//路径的顶部结点
    path_size[maxn],//路径的长度
    path_dep[maxn],//路径顶部结点的深度
    size[maxn],//每个节点的子树节点数
	bottom[maxn],//每个点对应的边
    belong[maxn],//每条边的路径编号
    rank[maxn],//结点在路径中的深度(最深的为1)
    w[maxn],
    path_count,n;
void addedge(int x,int y,int z,int num){
	int p;
	p=++edgeCnt;e[p].loc=y;e[p].code=num;
    w[num]=z;e[p].next=a[x];a[x]=p;
	p=++edgeCnt;e[p].loc=x;e[p].code=num;
	w[num]=z;e[p].next=a[y];a[y]=p;
}//构造树边
void dfs(int k,int dep){
	dep++;
	int p=a[k];size[k]=1;
	int max=0,j;
	while (p){
		if (e[p].loc !=father[k]){//边所连的结点不是父节点
			father[e[p].loc]=k;bottom[e[p].code]=e[p].loc;
			dfs(e[p].loc,dep);
			size[k]+=size[e[p].loc];
			if (size[e[p].loc]>max){
				max=size[e[p].loc];
				j=e[p].loc;
			}
		}
		p=e[p].next;
	}
	p=a[k];belong[k]=0;
	while (p){
		if (e[p].loc != father[k]){
			if (e[p].loc==j){
				belong[k]=belong[e[p].loc];
				rank[k]=rank[e[p].loc]+1;
			}
			else{
				int i=belong[e[p].loc];
				path_dep[i]=dep;
				path_size[i]=rank[e[p].loc];
				path_top[i]=e[p].loc;
			}
		}
		p=e[p].next;
	}
	if (belong[k]==0){
		belong[k]=++path_count;
		rank[k]=1;
	}//叶节点自为一条路径
}
void build(int p,int l,int r){
	node[p].l=l;
	node[p].r=r;
	node[p].max=-infi;
	if (r-1>l){
		node[p].lch=++nodeCnt;
		build(node[p].lch,l,(l+r)/2);
		node[p].rch=++nodeCnt;
		build(node[p].rch,(l+r)/2,r);
	}
	else node[p].lch=node[p].rch=0;
}
void change(int p,int l,int data){
	if (l<=node[p].l && node[p].r<=l+1)
		node[p].max=data;
    else{
		if (l<(node[p].l+node[p].r)/2)change(node[p].lch,l,data);
			else change(node[p].rch,l,data);
		node[p].max=max(node[node[p].lch].max,node[node[p].rch].max);
	}
}
int ask(int p,int l,int r){
	int t1,t2;
	if (l<=node[p].l && node[p].r<=r)return (node[p].max);
	else {
		t1=t2=-infi;
		if (l<(node[p].l+node[p].r)/2)t1=ask(node[p].lch,l,r);
		if (r>(node[p].l+node[p].r)/2)t2=ask(node[p].rch,l,r);
		return max(t1,t2);
	}
}//线段树的基本操作
void prepare(){
	path_count=father[1]=0;
	dfs(1,0);
	int i=belong[1];
	path_dep[i]=0;
	path_size[i]=rank[1];
	path_top[i]=1;
	for (int i=1;i<=path_count;i++){
		tree[i]=++nodeCnt;
		build(tree[i],1,path_size[i]+1);
	}//为每一条路径构造线段树
	for (int i=1;i<n;i++)
		change(tree[belong[bottom[i]]],rank[bottom[i]],w[i]);//赋权值
}//树链剖分的主过程
void work(int x,int y){
	change(tree[belong[bottom[x]]],rank[bottom[x]],y);
}
int query(int a,int b){
	int max=-infi,x=belong[a],y=belong[b];
	int k;
	while (x!=y){
		if (path_dep[x]>path_dep[y]){
			k=ask(tree[x],rank[a],path_size[x]+1);
			if (k>max)max=k;
			a=father[path_top[x]];
			x=belong[a];
		}
		else{
			k=ask(tree[y],rank[b],path_size[y]+1);
			if (k>max)max=k;
			b=father[path_top[y]];
			y=belong[b];
		}
	}
	if (rank[a]!=rank[b]){
		if (rank[a]>rank[b])k=ask(tree[belong[a]],rank[b],rank[a]);
			else k=ask(tree[belong[a]],rank[a],rank[b]);
		if (k>max)max=k;
	}
	return max;
}
int main(){
	int T;
	char cmd;
	scanf("%d",&T);
	while (T--){
		scanf("%d",&n);
		for (int i=1;i<n;i++){
			int x,y,z;
			scanf("%d %d %d\n",&x,&y,&z);
			addedge(x,y,z,i);
		}
		prepare();
		while (true){
			cmd=getchar();
			if (cmd=='D')break;
			int x,y;
			scanf("%d %d\n",&x,&y);
			if (cmd=='Q')printf("%d\n",query(x,y));
				else work(x,y);
		}
	}
	return 0;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值