SPOJ Query on a tree——(树链剖分和LCT动态树)

You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3...N-1.

We will ask you to perfrom some instructions of the following form:

  • CHANGE i ti : change the cost of the i-th edge to ti
    or
  • QUERY a b : ask for the maximum edge cost on the path from node a to node b

Input

The first line of input contains an integer t, the number of test cases (t <= 20). t test cases follow.

For each test case:

  • In the first line there is an integer N (N <= 10000),
  • In the next N-1 lines, the i-th line describes the i-th edge: a line with three integersa b c denotes an edge betweena,b of costc (c <= 1000000),
  • The next lines contain instructions "CHANGE i ti" or "QUERY a b",
  • The end of each test case is signified by the string "DONE".

There is one blank line between successive tests.

Output

For each "QUERY" operation, write one integer representing its result.

Example

Input:
1

3
1 2 1
2 3 2
QUERY 1 2
CHANGE 1 3
QUERY 1 2
DONE

Output:
1
3


题意:给定一棵树,告诉了每条边的权值,然后给出两种操作:

(1)把第i条边的权值改为val

(2)询问a,b路径上权值最大的边

转化为用边的孩子节点来表示该边。



方法一:树链剖分




两次dfs
第一次dfs就是找重边,也就是记录下所有的重边。

第二次dfs就是连接重边形成重链,具体过程就是:以根节点为起点,沿着重边向下拓展,拉成重链,不在当前重链上的节
点,都以该节点为起点向下重新拉一条重链。

数组作用:

siz[]数组,用来保存以x为根的子树节点个数

top[]数组,用来保存当前节点的所在链的顶端节点

son[]数组,用来保存重儿子

dep[]数组,用来保存当前节点的深度

fa[]数组,用来保存当前节点的父亲

tid[]数组,用来保存树中每个节点剖分后的新编号

rank[]数组,用来保存当前节点在线段树中的位置(这题不需要)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define mem(a) memset(a,0,sizeof(a))
#define Size 100000
#define inf 99999999
using namespace std;
struct node{
	int x,y,w;
}tree[Size];
int be[Size<<1],ne[Size<<1],to[Size<<1],w[Size],e;
void add(int x,int y,int z){to[++e]=y;ne[e]=be[x];be[x]=e;w[e]=z;}
int son[Size],fa[Size],size[Size],top[Size];
int deep[Size],tid[Size],cnt;
int a[Size],MAX[Size<<2];
bool p[Size];
void init(){
	mem(be);
	mem(son);
	cnt=e=0;
}
//树链剖分 
void dfs1(int x,int father,int deepth){
	size[x]=1;
	fa[x]=father;
	deep[x]=deepth;
	p[x]=1;
	for(int i=be[x];i;i=ne[i]){
		if(!p[to[i]]){
			dfs1(to[i],x,deepth+1);
			size[x]+=size[to[i]];
			if(!son[x] || size[to[i]]>size[son[x]])//设置重儿子 
				son[x]=to[i];
		}
	}
}
void dfs2(int x,int father){
	top[x]=father;//重链中的顶点 
	tid[x]=++cnt;//剖分后点的编号 
	p[x]=1;	
	if(son[x]==0)return;
	dfs2(son[x],father);//先搜重儿子 
	for(int i=be[x];i;i=ne[i]){//搜轻儿子 
		if(!p[to[i]] && to[i]!=son[x]){
			dfs2(to[i],to[i]);//轻链中自己为自己的top 
		}
	}
}
//线段树
//普通线段树操作 
void build_tree(int h,int l,int r){
	if(l==r){MAX[h]=a[l];return;}
	int mid=(l+r)>>1;
	build_tree(h<<1,l,mid);
	build_tree(h<<1|1,mid+1,r);
	MAX[h]=max(MAX[h<<1],MAX[h<<1|1]);
}
void update(int h,int l,int r,int x,int w){
	if(l==x && r==x){MAX[h]=w;return;}
	int mid=(l+r)>>1;
	if(x<=mid)update(h<<1,l,mid,x,w);
	else update(h<<1|1,mid+1,r,x,w);
	MAX[h]=max(MAX[h<<1],MAX[h<<1|1]);
}
int Query(int h,int l,int r,int s,int e){
	if(l==s && r==e){return MAX[h];}
	int mid=(l+r)>>1;
	if(e<=mid)return Query(h<<1,l,mid,s,e);
	else if(s>mid)return Query(h<<1|1,mid+1,r,s,e);
	else return max(Query(h<<1,l,mid,s,mid),Query(h<<1|1,mid+1,r,mid+1,e));
}
//将树链剖分转化为线段树 
void change(int n,int x,int y){
	if(deep[tree[x].x]>deep[tree[x].y])//较深的点表示的边 
		update(1,2,n,tid[tree[x].x],y);
	else
		update(1,2,n,tid[tree[x].y],y);
}
int query(int n,int x,int y){
	int Max=-inf;
	while(top[x]!=top[y]){//不断转化到一条重链上 
		if(deep[top[x]]<deep[top[y]])swap(x,y);//重链顶点的深度比较 
		Max=max(Max,Query(1,2,n,tid[top[x]],tid[x]));
		x=fa[top[x]];
	}
	if(deep[x]<deep[y])swap(x,y);
	if(x!=y)Max=max(Max,Query(1,2,n,tid[y]+1,tid[x]));//要+1,变到下一个边 
	return Max;
}
int main(){
	int Case;
	scanf("%d",&Case);
	while(Case--){
		init();
		int n;
		scanf("%d",&n);
		for(int i=1;i<n;i++){
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			tree[i].x=x;tree[i].y=y;tree[i].w=z;
			add(x,y,z);add(y,x,z);
		}
		mem(p);
		dfs1(1,1,1);
		mem(p);
		dfs2(1,1);
		for(int i=1;i<n;i++){
			if(deep[tree[i].x]>deep[tree[i].y])//较深的点表示连接的边 
				a[tid[tree[i].x]]=tree[i].w;
			else
				a[tid[tree[i].y]]=tree[i].w;
		}
		build_tree(1,2,n);
		while(1){
			static char ch[10];
			static int x,y;
			scanf("%s",&ch);
			if(ch[0]=='D')break;
			scanf("%d%d",&x,&y);
			if(ch[0]=='C')
				change(n,x,y);
			else
				printf("%d\n",query(n,x,y));
		}
	}
	return 0;
}

方法二: LCT

先将将边与点相连,使得边在树中,方便操作,更改时直接更改边即可。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define F(x) tree[x].fa
#define LC(x) tree[x].child[0]
#define RC(x) tree[x].child[1]
#define REV(x) tree[x].rev
#define Size 300010
using namespace std;
inline int read(){
	int sum=0,fg=1;char c=getchar();
	while(c<'0' || c>'9'){if(c=='-')fg=-1;c=getchar();}
	while(c>='0' && c<='9'){sum=sum*10+c-'0';c=getchar();}
	return sum*fg;
}
struct lct{
	int fa,child[2],rev,add;
	int v,MAX;
}tree[Size];
int be[Size],ne[Size],to[Size],e;
struct link_cut_tree{
	inline bool isroot(int x){
		return LC(F(x))!=x && RC(F(x))!=x;
	}
	inline void pushup(int x){
		tree[x].MAX=max(tree[LC(x)].MAX,tree[RC(x)].MAX);
		tree[x].MAX=max(tree[x].MAX,tree[x].v);
	}
	inline void pushdown(int x){
		if(REV(x)){
			REV(x)^=1;REV(LC(x))^=1;REV(RC(x))^=1;
			swap(LC(x),RC(x));
		}
	}
	void Pushdown(int x){
		if(!isroot(x))Pushdown(F(x));
		pushdown(x);
	}
	inline void rotate(int x){
		int A=F(x),B=F(A);bool w=(RC(A)==x);
		if(!isroot(A)){
			if(LC(B)==A)LC(B)=x;
			else if(RC(B)==A)RC(B)=x;
		}
		F(tree[x].child[w^1])=A;F(A)=x;F(x)=B;
		tree[A].child[w]=tree[x].child[w^1];tree[x].child[w^1]=A;
		pushup(A);pushup(x);
	}
	inline void splay(int x){
		Pushdown(x);
		while(!isroot(x)){
			if(!isroot(F(x)))rotate(x);
			rotate(x);
		}
	}
	inline void access(int x){
		for(int i=0;x;i=x,x=F(x))splay(x),RC(x)=i,pushup(x);
	}
	inline int find_root(int x){
		access(x);splay(x);
		while(LC(x))x=LC(x);
		return x;
	}
	inline void reverse(int x){
		access(x);splay(x);REV(x)^=1;
	}
	inline void link(int x,int y,int k){
		reverse(x);F(x)=y;tree[y].v=k;
	}
	inline void cut(int x,int y){
		reverse(x);access(y);splay(y);
		F(LC(y))=0;LC(y)=0;
	}
	inline void update(int x,int w){
		access(x);splay(x);tree[x].v=w;
		pushup(x);
	}
	inline int query(int x,int y){
		reverse(x);access(y);splay(y);
		return tree[y].MAX;
	}
}LCT;
void add(int x,int y){to[++e]=y;ne[e]=be[x];be[x]=e;}
void dfs(int x,int fa){
	for(int i=be[x];i;i=ne[i]){
		int v=to[i];
		if(v!=fa){
			tree[v].fa=x;
			dfs(v,x);
		}
	}
}
void init(){
	memset(be,0,sizeof(be));e=0;
	memset(tree,0,sizeof(tree));
}
struct node{
	int x,y,v;
}a[Size];
int main(){
	int Case=read();
	while(Case--){
		init();
		int n=read();
		for(int i=1;i<n;i++){
			int x=read(),y=read(),val=read();
			a[i].x=x,a[i].y=y;a[i].v=val;
			add(i+n,x);add(x,i+n);add(i+n,y);add(y,i+n);
		}
		char tp[10];
		dfs(1,1);
		for(int i=1;i<n;i++)
			LCT.update(i+n,a[i].v);
		while(1){
			scanf("%s",tp);
			if(tp[0]=='D')break;
			else if(tp[0]=='C'){
				int x=read(),val=read();
				LCT.update(x+n,val);
			}
			else{
				int x=read(),y=read();
				printf("%d\n",LCT.query(x,y));
			}
		}
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值