Link-Cut Tree 学习小记

【关于LCT的标记】

1. 在查询的时候传递标记与Splay时按从祖到下地传递标记是等价的。

2. 在将一个点上旋之前一定要把身上的标记传递出去。

3. 挂上标记没有必要立即生效。

4. 切忌从虚父亲处获得标记。(不是同一条重链)

5. 注意像乘0这样的标记。

【关于改变树形态和Splay】

1. 改之前要确保标记都传完了。

2. Cut过程要把虚父亲删除,Link过程构成轻链后没必要update。

3. 改完以后一定要确保都Splay或Update了。(保证数据同步性)

【关于数据上传过程】

1. 如果标记不是马上生效的,在Update时要算上标记。

2. 结构改变一定要记得Update。

3. 没有必要专门开一个Cnt 域来处理类似区间加这样的标记,传完标记后update一下就好了。


【实现细节】

1. 虚父亲没必要专门开一个域来记录,只需要判定其父亲是否有一个儿子为它本身即可判断是否为虚父亲,可以写一个短的函数 OnTop( x ) 来记录。

2. Splay结点x到当前链顶,停止条件应该是OnTop( x )。

3. 若其父亲的父亲为根,进行双旋的话有可能会让这个祖先降两层。

4. Splay过程只需判断父亲为链顶,或同向则可以判定为单旋还是双旋,若要避免3中的情况只需要判爷爷是否为链顶即可。

5. Cut, Query之类的过程如无必要不要判定谁上谁下,Evert一下就可以简单地实现了。

6. 封装得好看起来比较不违和,不封装看起来比较短。


代码:

#include <fstream>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 200005;

#define fo( i, x, y ) for ( int i=x; i<y; ++i )

ifstream in( "tree.in" );
ofstream out( "tree.out" );

int v[N];
int n, Q, PCNT;

struct LCT
{
	struct Splay
	{
		int Fa, s[2];
		bool Rev;
	} g[N];

	inline bool OnTop( int x )
	{
		int F = g[x].Fa;
		return !( g[F].s[0]==x || g[F].s[1]==x );
	}
	inline void push( int x )
	{
		++PCNT;
		if ( !g[x].Rev || !x ) return;
		fo ( i, 0, 2 ) g[ g[x].s[i] ].Rev ^= 1;
		swap( g[x].s[0], g[x].s[1] ), g[x].Rev = 0;
	}
	inline void Rotate( int x )
	{
		int y = g[x].Fa, z = g[y].Fa;
		bool p = ( g[y].s[1]==x ), q = p ^ 1;
		int A = g[x].s[q];
		g[y].Fa = x, g[y].s[p] = A, g[x].Fa = z;
		g[x].s[q] = y, g[A].Fa = y;
		if ( g[z].s[0]==y ) g[z].s[0] = x;
		else if ( g[z].s[1]==y ) g[z].s[1] = x;
	}
	void Splay( int x )
	{
		push( x );
		for ( int y=g[x].Fa, z=g[y].Fa; !OnTop( x ); y=g[x].Fa, z=g[y].Fa )
		{
			push( z ), push( y ), push( x );
			if ( OnTop( y ) || OnTop( z ) || g[z].s[0]==y ^ g[y].s[0]==x ) Rotate( x );
			else Rotate( y ), Rotate( x );
		}
	}
	inline void Access( int x )
	{
		int Pre = 0;
		do
		{
			Splay( x );
			g[x].s[0] = Pre;
			Pre = x, x = g[x].Fa;
		} while ( x );
	}
	inline void Evert( int x )
	{
		Access( x );
		Splay( x );
		g[x].Rev ^= 1;
	}
	inline void Link( int x, int y )
	{
		Evert( y );
		g[y].Fa = x;
	}
	inline void Cut( int x, int y )
	{
		Evert( x ), Access( y ), Splay( y );
		g[y].s[1] = g[ g[y].s[1] ].Fa = 0;
	}
	inline bool query( int x, int y )
	{
		if ( x==y ) return 1;
		Evert( x ), Access( y ); 
		Splay( x ), Splay( y );
		return g[y].s[1]==x;
	}
} f;

void preprocessing()
{
	ios :: sync_with_stdio( 0 );
	in >> n >> Q;
	fo ( i, 1, n + 1 ) in >> v[i];
	int x, y;
	fo ( i, 1, n )
	{
		in >> x >> y;
		f.Link( x, y );
	}
}

void solve()
{
	int Type, x, y, LasAns = 0;
	fo ( Case, 0, Q )
	{
		in >> Type >> x >> y;
		x ^= LasAns, y ^= LasAns;
		if ( Type==1 ) f.Cut( x, y );
		else if ( Type==2 ) 
		{
			if ( f.query( x, y ) ) LasAns = v[x] * v[y];
			else LasAns = v[x] + v[y];
			out << LasAns << endl;
		}
		else v[x] = y;
	}
	out << PCNT << endl;
}

int main()
{
	preprocessing();
	solve();
	in.close(); out.close();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值