Link-Cut Tree 学习小记

本文深入探讨了LCT(Link-Cut Tree)算法的核心概念、实现细节以及数据上传过程,详细介绍了标记、树形态改变、Splay操作、数据同步性等方面的知识。通过实例代码,清晰展示了如何使用LCT解决特定问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【关于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、付费专栏及课程。

余额充值