LCT动态树学习笔记--基本操作(带详细注释)

一些LCT最基本也是最核心的操作,其他一些维护树链信息的可以直接参照树链剖分。

#include<bits/stdc++.h>
using namespace std;
namespace LCT{
	struct gg{
		int ch[2],fa;
		bool rev;
	}node[10010];
	void init()
	{for(int i=0;i<=10000;i++)node[i].ch[0]=node[i].ch[1]=node[i].fa=node[i].rev=0;}//毫无卵用的清空 
	int get(int x)//询问该点是父亲的左儿子或右儿子或都不是(都不是即该点为所在splay的根,通过轻边单向连父亲) 
	{
		if(!node[x].fa)return -1;
		if(node[node[x].fa].ch[0]==x)return 0;
		if(node[node[x].fa].ch[1]==x)return 1;
		return -1;
	}
	void push_down(int x)//下放翻转标记,同普通splay 
	{
		if(x&&node[x].rev)
		{
			node[node[x].ch[0]].rev^=1;
			node[node[x].ch[1]].rev^=1;
			swap(node[x].ch[0],node[x].ch[1]);
			node[x].rev=0;
		}
	}
	void push(int x)//下放x点到所在splay的根的所有标记 
	{
		if(get(x)!=-1)push(node[x].fa);
		push_down(x);
	}
	void rotate(int x)//splay基本操作,注意判断父亲是所在splay根的情况 
	{
		int fx=node[x].fa,gx=node[fx].fa,op=get(x),fop=get(fx);
		node[fx].ch[op]=node[x].ch[op^1],node[node[x].ch[op^1]].fa=fx;
		node[x].fa=gx;if(fop!=-1)node[gx].ch[fop]=x;
		node[x].ch[op^1]=fx,node[fx].fa=x;
	}
	void splay(int x)//splay基本操作,注意先下放标记 
	{
		push(x);
		for(int fx=node[x].fa;get(x)!=-1;rotate(x),fx=node[x].fa)
		{
			if(get(fx)!=-1)
			{
				(get(x)==get(fx))?rotate(fx):rotate(x);
			}
		}
	}
	void access(int x)//最重要操作,即我在前一篇说的更改轻重链,它能将x节点与整棵树根(不是所在splay)连到一个重链里
//并且x节点和儿子都以虚边连接以支持换根
	{
		for(int y=0;x;)
		{
			splay(x);//先转到当前splay的根 
			node[x].ch[1]=y;//右儿子断开,变为上次转到splay的点 
			y=x,x=node[x].fa;//更新 
		}
	}
	void make_root(int x)//换根操作,令x成为整棵树根(基于access后x为所在splay最深点性质) 
	{
		access(x),splay(x),node[x].rev^=1;//打标记是因为x变为根后它的深度要变为最小,因此需翻转 
	}
	void split(int x,int y)//把x与y的路径组成一个以y为根的splay
	{
		make_root(x),access(y),splay(y);
	}
	void cut(int x,int y)//把x与y分离 
	{
		split(x,y);
		node[x].fa=node[y].ch[0]=0;
	}
	void line(int x,int y)//连接x,y 
	{
		make_root(x),node[x].fa=y;
	}
	int find(int x)//查找x点所在树的根节点 
	{
		for(access(x),splay(x);node[x].ch[0];x=node[x].ch[0]);
		return x;
	}
}
using namespace LCT;
int main()
{
	int n,m,x,y;char op[20];
	scanf("%d%d",&n,&m);
	init();
	while(m--)
	{
		scanf("%s",op);
		scanf("%d%d",&x,&y);
		if(op[0]=='Q')find(x)==find(y)?puts("Yes"):puts("No");
		else if(op[0]=='C')line(x,y);
		else if(op[0]=='D')cut(x,y);
	}
}

upd:

登录—专业IT笔试面试备考平台_牛客网

维护到根路径上点权最值,优化码风(看以前的代码写成一坨跟yzy一样,真是难受)

#include<bits/stdc++.h>
#define pii pair<int,int>
#define fi first
#define sc second
#define pb push_back
#define ll long long
#define trav(v,x) for(auto v:x)
#define all(x) (x).begin(), (x).end()
#define VI vector<int>
#define VLL vector<ll>
#define pll pair<ll, ll>
#define double long double
#define int long long
using namespace std;
const int N = 1e6 + 100;
const ll inf = 1e9;
const ll mod = 998244353;//1e9 + 7;

#ifdef LOCAL
void debug_out(){cerr << endl;}
template<typename Head, typename... Tail>
void debug_out(Head H, Tail... T)
{
	cerr << " " << to_string(H);
	debug_out(T...);
}
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

namespace LCT{
	#define ls ch[0]
	#define rs ch[1]
	int tot;
	struct gg{
		int ch[2], fa, las, val, mx, ps;
		bool rev;
		void in(int x, int y)
		{
			las = x, val = y, ps = tot;
			ch[0] = ch[1] = fa = mx = 0;
			rev = 0;
		}	
	}node[N];
	void init()
	{
		for(int i = 0; i <= tot; i++)
		{
			node[i].ch[0] = node[i].ch[1] = node[i].fa = node[i].rev = 0;
			node[i].val = node[i].las = node[i].mx = 0;
		}
		tot = 0;
	}//毫无卵用的清空 
	int get(int x)//询问该点是父亲的左儿子或右儿子或都不是(都不是即该点为所在splay的根,通过轻边单向连父亲) 
	{
		if(!node[x].fa)
			return -1;
		if(node[node[x].fa].ch[0]==x)
			return 0;
		if(node[node[x].fa].ch[1]==x)
			return 1;
		return -1;
	}
	void push_up(int x)
	{
		node[x].mx = node[x].val, node[x].ps = x;
		int l = node[x].ls, r = node[x].rs;
		if(l && node[x].mx < node[l].mx)
			node[x].mx = node[l].mx, node[x].ps = node[l].ps;
		if(l && node[x].mx == node[l].mx && node[x].ps < node[l].ps)
			node[x].mx = node[l].mx, node[x].ps = node[l].ps;
		swap(l, r);
		if(l && node[x].mx < node[l].mx)
			node[x].mx = node[l].mx, node[x].ps = node[l].ps;
		if(l && node[x].mx == node[l].mx && node[x].ps < node[l].ps)
			node[x].mx = node[l].mx, node[x].ps = node[l].ps;
	}
	void push_down(int x)//下放翻转标记,同普通splay 
	{
		if(x && node[x].rev)
		{
			node[node[x].ch[0]].rev^=1;
			node[node[x].ch[1]].rev^=1;
			swap(node[x].ch[0],node[x].ch[1]);
			node[x].rev=0;
		}
	}
	void push(int x)//下放x点到所在splay的根的所有标记 
	{
		if(get(x) != -1)push(node[x].fa);
		push_down(x);
	}
	void rotate(int x)//splay基本操作,注意判断父亲是所在splay根的情况 
	{
		int fx = node[x].fa, gx = node[fx].fa, op = get(x), fop = get(fx);
		node[fx].ch[op] = node[x].ch[op ^ 1], node[node[x].ch[op ^ 1]].fa = fx;
		node[x].fa = gx;
		if(fop!=-1)
			node[gx].ch[fop] = x;
		node[x].ch[op ^ 1] = fx, node[fx].fa = x;
		push_up(fx);
		push_up(x);
	}
	void splay(int x)//splay基本操作,注意先下放标记 
	{
		push(x);
		for(int fx = node[x].fa; get(x) != -1; rotate(x), fx = node[x].fa)
		{
			if(get(fx)!=-1)
			{
				(get(x)==get(fx))?rotate(fx):rotate(x);
			}
		}
	}
	void access(int x)//最重要操作,即我在前一篇说的更改轻重链,它能将x节点与整棵树根(不是所在splay)连到一个重链里
//并且x节点和儿子都以虚边连接以支持换根
	{
		for(int y = 0; x;)
		{
			splay(x);//先转到当前splay的根 
			node[x].ch[1] = y;//右儿子断开,变为上次转到splay的点 
			push_up(x);
			y = x, x = node[x].fa;//更新 
		}
	}
	void make_root(int x)//换根操作,令x成为整棵树根(基于access后x为所在splay最深点性质) 
	{
		access(x), splay(x), node[x].rev ^= 1;//打标记是因为x变为根后它的深度要变为最小,因此需翻转 
	}
	void split(int x, int y)//把x与y的路径组成一个以y为根的splay
	{
		make_root(x), access(y), splay(y);
	}
	void cut(int x,int y)//把x与y分离 
	{
		split(x,y);
		node[x].fa = node[y].ch[0] = 0;
	}
	void line(int x,int y)//连接x,y 
	{
		node[x].fa=y;
	}
	int find(int x)//查找x点所在树的根节点 
	{
		for(access(x), splay(x); node[x].ch[0]; x = node[x].ch[0]);
		return x;
	}
	int find_ps(int x)
	{
		split(x, 1);
		if(node[1].mx <= 0)
			return -1;
		return node[1].ps;
	}
}
using namespace LCT;

int idx[N];
int qq;

pll ask(int x, int y)
{
	ll res = 0, num = 0;
	while(y)
	{
		int ps = find_ps(x);
		//cerr << ps << ' ' << node[ps].val << ' ' << node[ps].las << '\n';
		if(ps < 0)
			break;
		int tmp = min(node[ps].las, y);
		node[ps].las -= tmp;
		y -= tmp;
		num += tmp;
		res += 1LL * node[ps].val * tmp;
		if(node[ps].las == 0)
		{
			make_root(ps);
            node[ps].las = 0;
            node[ps].val = -1;
            node[ps].mx = -1;
		}
	}
	return pll(res, num);
}

void sol()
{
	tot = N - 1;
	init();
	idx[0] = 1;
	cin >> qq;
	int x, y;
	cin >> x >> y;
	node[++tot].in(x, y);
	ll ans = 0;
	//cerr << node[1].fa << '\n';
	for(int i = 1; i <= qq; i++)
	{
		int op;
		cin >> op;
		if(op == 1)
		{
			int fa;
			cin >> fa;
			fa = (fa + ans) % i;
			fa = idx[fa];
			cin >> x >> y;
			idx[i] = ++tot;
			node[tot].in(x, y);
			//cerr << ">>" << fa << ' ' << x << ' ' << y << '\n';
			line(tot, fa);
		}
		else
		{
			cin >> x >> y;
			pll cur = ask(idx[x], y);
			//cerr << "!!" << cur.sc << ' ' << cur.fi << '\n';
			cout << cur.sc << ' ' << cur.fi << '\n';
			ans = cur.sc + cur.fi;
		}
	}
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
//	int tt;
//	cin >> tt;
//	while(tt--)
		sol();
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值