LCT的初步理解

一种动态树,可以处理动态问题的算法。

这是我个人的观点~~


有大神也是这么说的,像其他算法比如树链剖分,只能处理静态的数据或者在轻重链上的边或点的权值,对于其他动态的处理就毫无办法了。

而用裸的暴力绝对是要T的。


怎么办,引入LCT~~~~


LCT用来维护动态的森林,以及一些链上操作。


说实话初次接触LCT的时候,由于是0基础,然后左看右看不懂网上的题解,然后终于找到了论文,看了几遍才勉强懂这个概念~~(蒟蒻- -)


推荐论文:http://wenku.baidu.com/view/75906f160b4e767f5acfcedb

LCT是处理节点无序的有根树组成的森林,进行一些列操作(例如合并,剪切,翻转,更新·····)


对于一棵树的操作,我们可以用splay维护;同理,对于一片森林,也可以用多棵splay维护,而LCT就是要把这些森林的splay联系在一起。

而LCT的核心是access操作~

参照网上的意见,以这道题入手:

SPOJ - QTREE
Query on a tree
Time Limit: 851MS   Memory Limit: 1572864KB   64bit IO Format: %lld & %llu

Submit Status

Description

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 integers a b c denotes an edge between ab of cost c (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

Hint

Added by: Thanh-Vy Hua
Date: 2005-06-08
Time limit: 0.851s
Source limit: 15000B
Memory limit: 1536MB
Cluster: Cube (Intel G860)
Languages: ADA ASM BASH BF C C# C++ 5 CLPS LISP sbcl LISP clisp D FORT HASK ICON ICK JAVA LUA NEM NICE CAML PAS gpc PAS fpc PERL PHP PIKE PRLG PYTH 2.7 RUBY SCM qobi SCM guile ST TEXT WSPC

Submit Status


咦,这道题不是树链剖分的入门题么?  对的,不过,也可以用LCT做(树链剖分只处理静态的树和有关轻重边的动态更新,而LCT就是用来解决动态树问题)


题意:

给定一棵树,给定每条边a,b,w  (w权值)

完成两个操作:

1.把第i条边权值改成w

2.查询u到v路径上的最大权值


介绍一下access操作:查询根到u这条路径,并把这条路径更新为偏爱路径(之前的偏爱边可能会有改变)被访问到的u,不再有偏爱儿子。

每次access访问,都会把这条路径上的点用一棵splay维护,splay维护的关键词是点的深度,保证左边的子树比当前点深度小,右边的子树比当前点深度大。

并且这棵树把u作为根节点之后不再有右儿子(u没有偏爱儿子).


而splay要维护的是一条偏爱路径上的点。所有的splay会构成一个森林的集合。


要注意的是,在rotate判断ff的时候,有可能ff的左右儿子都不是f,然而没有进行判断就会跑出神奇的错误,我第一次写就挂在了这里,调了好久。

LCT的初步理解_第1张图片


剩下的代码需要积累出技巧,慢慢入门- -

#include
#include
#include
#include
#include
#include
#include
using namespace std;
/*
LCT的思想类似于轻重链剖分 
是用偏爱路径划分,用splay维护一条偏爱路径,然后像森林操作一样搞 
u-v边的权值记录在v点上 
*/
#define maxn 10005
int n;
int fa[maxn];
struct edge
{
	int v,w;
	edge *next;
	edge(int _v,int _w,edge *_next)
	{
		v=_v;
		w=_w;
		next=_next;
	}
}*head[maxn];
struct node
{
	node *f;
	node *ch[2];
	bool root;//是否是所在辅助树的根节点 
	int cost;
	int maxcost;
	
}tree[maxn],*null,Tnull;
void init(node *u)
{
	u->f=u->ch[0]=u->ch[1]=null;
	u->root=true;
	u->cost=u->maxcost=0;
}
void pushup(node *p)
{
	p->maxcost=max(max(p->ch[0]->maxcost,p->ch[1]->maxcost),p->cost);
}
void rotate(node *u)
{
	node *f=u->f;	
	node *ff=f->f;
	int d=u==f->ch[1];
	
	f->ch[d]=u->ch[d^1];
	if(u->ch[d^1]!=null)u->ch[d^1]->f=f;
	
	u->f=ff;
	if(ff!=null)
	{
		if(ff->ch[0] == f)
		{
			ff->ch[0] = u;
		}
		else if(ff->ch[1] == f)//一定要用Else If,如果直接用Else会出现错误,因为树本身可能不是二叉树,虽然生成的Splay Tree是
		{
			ff->ch[1] = u;
		}
	}
	
	u->ch[d^1]=f;
	f->f=u;
	
	pushup(f);
	pushup(u);
	if(f->root)
	{
		u->root=true;
		f->root=false;
	}
}
void splay(node *u)
{
	if(u==null)return ;
	while(!u->root)
	{
		node *f=u->f;		
		if(f->root)
		{
			rotate(u);
		}
		else
		{
			node *ff=f->f;
		    int d=u==f->ch[1];
	    	int dd=f==ff->ch[1];
	    	if(d==dd)rotate(f);
	    	else rotate(u);
	    	rotate(u);
		}		
	}
	pushup(u);
}
vector >E;
char ss[20];
void access(node *u,bool flag)
{
	node *v=null;
	while(u!=null)
	{
		splay(u);
		if(flag)
	    {
	    	if(u->f==null)
	    	{
	    		printf("%d\n",max(u->ch[1]->maxcost,v->maxcost));
	    	}
    	}
    	u->ch[1]->root=true;
    	u->ch[1]=v;
    	v->root=false;
		pushup(u);
    	v=u;
    	u=u->f;
	}	
}
void query(int u,int v)
{
	access(tree+u,0);
	access(tree+v,1);
}
void change(int u,int w)
{
	access(tree+u,0);
	splay(tree+u);
	tree[u].cost=w;
	pushup(tree+u);
}
void bfs()
{
	memset(fa,-1,sizeof(fa));
	queueq;
	q.push(1);
	fa[1]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(edge *i=head[u];i;i=i->next)
		{
			if(fa[i->v]==-1)
			{
				fa[i->v]=u;
				tree[i->v].f=tree+u;
				tree[i->v].cost=tree[i->v].maxcost=i->w;
				q.push(i->v);
			}
		}
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	null=&Tnull;
	init(null);
	while(T--) 
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			head[i]=NULL;
			init(&tree[i]);
		}
		E.clear();
		for(int i=1;i



然后,再来做操作多一点的:http://acm.hdu.edu.cn/showproblem.php?pid=4010

给定一棵树,维护4个操作:

1.给定u,v,如果不在一棵树上,它们之间连边。

2.给定u,v如果u!=v且在同一棵树上,把u设为整棵树的根,再把v和它的父亲断开。

3.给定u,v,w,如果u,v在同一条树上,把他们之间的点的权值+w

4.给定u,v,如果u,v在同一棵树上,求出他们路径上的最大值。


违法操作输出-1



连边操作,访问u,把u旋到根,把它左右翻转即可(翻转前splay上u一定没有右节点(没有偏爱儿子),翻转后一定没有左儿子,说明它就是从根到u路径上深度最小的了,就成为了根)

把它接到v上

设为整棵树的根:一样的先access(u),splay(u)再rev(u)。

断开:access(u),splay(u),但不翻转,这样u没有右儿子但有左儿子,和左子树断开即可。

对于3、4操作,可以先把u设为根,再access(v)然后找到u-v这条路径上splay的根节点在哪里,信息都记录在那个节点上!


我也是蒟蒻啊,才开始慢慢理解LCT的access.


#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=300000+20;
const int inf=0x3f3f3f3f;
struct node
{
	node *f;
	node *ch[2];
	bool rev;
	int add;
	int mm;
	int key;
}tree[maxn],*null,*cur;
void init()
{
	null=tree;
	null->f=null->ch[0]=null->ch[1]=null;
	null->rev=0;
	null->add=0;
	null->mm=null->key=-inf;
	cur=tree+1;
}
node *newnode(int key)
{
	cur->f=cur->ch[0]=cur->ch[1]=null;
	cur->mm=cur->key=key;
	cur->add=0;
	cur->rev=0;
	return cur++;
}
bool isroot(node *x)
{
	return x==null||x->f->ch[0]!=x&&x->f->ch[1]!=x;
}
void pushup(node *u)
{
	u->mm=max(u->key,max(u->ch[0]->mm,u->ch[1]->mm));
}
void pushdown(node *u)
{
	if(u==null)return ;
	if(u->rev)
	{
		swap(u->ch[0],u->ch[1]);
		if(u->ch[0]!=null)u->ch[0]->rev^=1;
		if(u->ch[1]!=null)u->ch[1]->rev^=1;
		u->rev=0;
	}
	if(u->add)
	{
		if(u->ch[0]!=null)
		{
			u->ch[0]->add+=u->add;
			u->ch[0]->mm+=u->add;
			u->ch[0]->key+=u->add;
		}
		if(u->ch[1]!=null)
		{
			u->ch[1]->add+=u->add;
			u->ch[1]->mm+=u->add;
			u->ch[1]->key+=u->add;
		}
		u->add=0;
	}
}
void rotate(node *u)
{
	node *f=u->f;	
	node *ff=f->f;
	int d=u==f->ch[1];
	
	if(u->ch[d^1]!=null)u->ch[d^1]->f=f;
	f->ch[d]=u->ch[d^1];
	
	u->f=ff;
	if(ff!=null)
	{
		if(f==ff->ch[0])ff->ch[0]=u;
		else if(f==ff->ch[1])ff->ch[1]=u;
	}
	
	u->ch[d^1]=f;
	f->f=u;
	
	pushup(f);
	pushup(u);
}
node *sta[maxn];
int cnt;
void splay(node *u)
{
	if(u==null)return ;
	cnt=1;
	sta[0]=u;
	for(node *y=u;!isroot(y);y=y->f)
	{
		sta[cnt++]=y->f;
	}
	while(cnt)pushdown(sta[--cnt]);
	while(!isroot(u))
	{
		node *f=u->f;
		node *ff=f->f;
		if(isroot(f))
		{
			rotate(u);
		}
		else
		{
			int d=u==f->ch[1];
			int dd=f==ff->ch[1];
			if(d==dd)rotate(f);
			else rotate(u);
			rotate(u);
		}
	}
	pushup(u);
}
node *access(node *u)
{
	node *v=null;
	while(u!=null)
	{
		splay(u);
		v->f=u;
		u->ch[1]=v;
		pushup(u);
		v=u;
		u=u->f;
	}
	return v;
}
void link(node *u,node *v)
{
	access(u);
	splay(u);
	u->rev=1;
	u->f=v;
}
bool sam(node *u,node *v)
{
	while(u->f!=null)u=u->f;
	while(v->f!=null)v=v->f;
	return u==v;
}
void changeroot(node *u)
{
	access(u)->rev^=1;
}
void cut(node *u)
{
	access(u);
	splay(u);//access+旋转之后左子树的点都比u小,一定会有u的父亲 
	u->ch[0]=u->ch[0]->f=null;
	pushup(u);
}
node *getroot(node *u)
{
	access(u);
	splay(u);
	while(u->f!=null)u=u->f;
	splay(u);
	return u;
}
int n,m;
int det[maxn];
struct edge
{
	int u,v;
}E[maxn];
int main()
{
	while(scanf("%d",&n)!=EOF)
	{
		init();
		for(int i=1;iadd+=w;
					q->mm+=w;
					q->key+=w;
				}
			}
			else if(k==4)
			{
				scanf("%d%d",&a,&b);
				if(!sam(tree+a,tree+b))printf("-1\n");
				else
				{
					changeroot(tree+a);
			    	access(tree+b);
			    	node *q=getroot(tree+b);
			    	printf("%d\n",q->mm);
				}		
			}
		}
		printf("\n");
	}
	return 0;
}



你可能感兴趣的:(数据结构,LCT)