Link Cut Tree

前言

很久没有写博客了, Z J O I 2019 ZJOI2019 ZJOI2019炸零快乐。
W R _ E t e r n i t y WR\_Eternity WR_Eternity大佬已经早早地学会了 L C T LCT LCT这种强大的数据结构,这让只会树剖的本蒟蒻内心慌地一逼,于是我看着他的博客,勉强自学了 L C T LCT LCT
至于时间复杂度的证明我这种蒟蒻当然只能选择略过了(逃。

蒟蒻自己的理解

树剖时我们选择重儿子是为了减少时间复杂度,但因为动态树中树的形态会变,那干脆就不要重儿子了,随便选一个儿子就好。
树剖时因为树剖序是固定的所以我们用线段树维护一般即可解决问题。但 L C T LCT LCT中会涉及到区间翻转,所以要用 S p l a y Splay Splay这种更为强大的数据结构做辅助树。

定义与约定

  • P r e f e r r e d   C h i l d Preferred\ Child Preferred Child:偏爱儿子,偏爱儿子与父亲节点在同一棵Splay中,每个节点最多只有一个偏爱儿子。
  • P r e f e r r e d   E d g e Preferred\ Edge Preferred Edge:偏爱边,连接父亲节点和偏爱儿子的边。
  • P r e f e r r e d   P a t h Preferred\ Path Preferred Path:偏爱链,由偏爱边及偏爱边连接的节点构成的链。
  • A u x i l i a r y   T r e e Auxiliary\ Tree Auxiliary Tree:辅助树,由一条偏爱链上的所有节点所构成的 S p l a y Splay Splay称作这条链的辅助树。
    每个点的键值为这个点的深度,即这棵 S p l a y Splay Splay 的中序遍历是这条链从链顶到链底的所有节点构成的序列
    辅助树的根节点的父亲指向链顶的父亲节点,然而链顶的父亲节点的儿子并不指向辅助树的根节点(也就是说父亲只认偏爱儿子,儿子都认父亲)
  • 实树 题目中要维护的那堆树
  • S p l a y Splay Splay中维护了子树的异或和、翻转标记。

一个大胆的假设

假设我们现在会两个操作。

  • a c c e s s ( u ) access(u) access(u) u u u 到实树根的路径变成偏爱路径,即把路径上的点都放到同一颗 S p l a y Splay Splay中。
  • m a k e _ r t ( u ) make\_rt(u) make_rt(u) 把实树的根变成 u u u

解决各类操作

Q u e r y ( u , v ) Query(u, v) Query(u,v)

即查询 u u u ~ v v v 路径上点权的异或和。
我们直接把 u u u 当成实树的根,然后把 v v v 到根的路径塞到同一颗 S p l a y Splay Splay 中,然后把 u u u 节点 s p l a y splay splay 到辅助树的根,查询 u u u 的子树异或和即可。

int query(const int &u, const int &v) {
	return make_rt(u), access(v), splay(v), sum[v];
}

F i n d _ r t ( u ) Find\_rt(u) Find_rt(u)

即查询 u u u 所在的实树的根节点。
我们把 u u u 到根节点上所有的点塞进一颗 S p l a y Splay Splay 中。
因为根的深度是最小的,所以把 u u u 节点 s p l a y splay splay 到辅助树的根,然后一直往左找即可,记得把标记下放即可。

int find_rt(int u) {
	access(u), splay(u);
	push_down(u);
	while(son[u][0]) u = son[u][0], push_down(u);
	return u;
}

L i n k ( u , v ) Link(u, v) Link(u,v)

即把 u u u v v v 两个节点连在一起,若 u u u v v v 已在同一棵树上则不做处理。
判断是否在同一棵实树中,只要判断实树的根是否相同即可。
若不在同一棵实树中,把 u u u 变成根,直接接成 v v v 的非偏爱儿子即可。

void link(const int &u, const int &v) {
	if(find_rt(u) != find_rt(v)) make_rt(u), fa[u] = v;
}

C u t ( u , v ) Cut(u, v) Cut(u,v)

即把 u u u 节点和 v v v 节点之间的连边断开,若 u u u v v v 不是直接相连则不做处理。
先判断是否有边相连。
u u u 变成根,把 v v v u u u 之间的路径塞到一棵 S p l a y Splay Splay 中,再把 v v v 节点 s p l a y splay splay 到辅助树的根。
如果 u u u v v v 之间有直接连边,因为 v v v 是 深度仅次于 u u u 的,那么此时 v v v 一定是 u u u 在辅助树上的父亲,而且 u u u 右子树为空,即没有深度介于 u ,   v u,\ v u, v 之间的点。
如果 u ,   v u,\ v u, v 之间有连边的话,将 v v v 的左儿子, u u u 的父亲赋值为空并更新 v v v 节点的 s u m sum sum 即可。

void cut(const int &u, const int &v) {
	make_rt(u), access(v), splay(v);
	if(son[v][0] != u || son[u][1]) return;
	fa[u] = son[v][0] = 0;
	update(v);
}

实现假设

假设终究是假设,我们还是要靠自己的双手把 a c c e s s access access m a k e _ r t make\_rt make_rt 实现的。

A c c e s s ( u ) Access(u) Access(u)

即把路径上的点都放到同一颗 S p l a y Splay Splay中。
要做的无非就两件事:

  • 把该节点与原来的偏爱儿子断开
  • 把该节点与父亲在辅助树上连起来
    不断重复即可。
void access(int u) {
	for(int v = 0; u; v = u, u = fa[u]) splay(u), son[u][1] = v, update(u);
}

M a k e _ r t ( u ) Make\_rt(u) Make_rt(u)

即把实树的根变成 u u u
很简单,把 u u u 到根的路径上的点塞到 S p l a y Splay Splay 中,把 u u u 节点 s p l a y splay splay 到辅助树的根,把整条链翻过来即可。

void make_rt(const int &u) {
	access(u), splay(u), reverse(u);
}

代码

#include <cstdio>
#include <algorithm>
#define enter putchar('\n')
using namespace std;
const int maxn = 3e5 + 5;
int fa[maxn], son[maxn][2], sum[maxn], w[maxn], rev[maxn];
template <typename T>
void read(T &x) {
	char ch = getchar(); bool f = 1; x = 0;
	while(ch < '0' || ch > '9') f &= ch != '-', ch = getchar();
	while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	x = f ? x : -x;
}
template <typename T>
void write(T x) {
	if(x < 0) x = -x, putchar('-');
	if(x > 9) write(x / 10);
	putchar(x % 10 + 48);
}
inline bool get(int u) { return son[fa[u]][1] == u; }
inline bool is_rt(int u) { return son[fa[u]][0] != u && son[fa[u]][1] != u; }
inline void reverse(int u) { rev[u] ^= 1, swap(son[u][0], son[u][1]); }
inline void update(int u) { sum[u] = sum[son[u][0]] ^ w[u] ^ sum[son[u][1]]; }
void push_down(int u) {
	if(rev[u]) {
		if(son[u][0]) reverse(son[u][0]);
		if(son[u][1]) reverse(son[u][1]);
		rev[u] = 0;
	}
}
void push_down_all(int u) {
	if(!is_rt(u)) push_down_all(fa[u]);
	push_down(u);
}
void rotate(int u) {
	int f = fa[u], gra = fa[f], d = get(u), k = son[u][d ^ 1];
	if(!is_rt(f)) son[gra][get(f)] = u;
	fa[u] = gra, son[u][d ^ 1] = f;
	fa[f] = u, son[f][d] = k;
	fa[k] = f;
	update(f), update(u);
}
void splay(int u) {
	push_down_all(u);
	for(; !is_rt(u); rotate(u))
		if(!is_rt(fa[u])) rotate(get(u) == get(fa[u]) ? fa[u] : u);
}
void access(int u) {
	for(int v = 0; u; v = u, u = fa[u]) splay(u), son[u][1] = v, update(u);
}
void make_rt(const int &u) { access(u), splay(u), reverse(u); }
int query(const int &u, const int &v) {
	return make_rt(u), access(v), splay(v), sum[v];
}
int find_rt(int u) {
	access(u), splay(u);
	push_down(u);
	while(son[u][0]) u = son[u][0], push_down(u);
	return u;
}
void link(const int &u, const int &v) {
	if(find_rt(u) != find_rt(v)) make_rt(u), fa[u] = v;
}
void cut(const int &u, const int &v) {
	make_rt(u), access(v), splay(v);
	if(son[v][0] != u || son[u][1]) return;
	fa[u] = son[v][0] = 0;
	update(v);
}
int main() {
	int n, q;
	read(n), read(q);
	for(int u = 1; u <= n; u++) read(w[u]);
	while(q--) {
		int opt;
		read(opt);
		if(opt == 0) {
			int u, v;
			read(u), read(v);
			write(query(u, v)), enter;
		} else if(opt == 1) {
			int u, v;
			read(u), read(v);
			link(u, v);
		} else if(opt == 2) {
			int u, v;
			read(u), read(v);
			cut(u, v);
		} else {
			int u, k;
			read(u), read(k);
			w[u] = k;
			splay(u);
		}
	}
	return 0;
}
内容概要:本文详细介绍了基于滑模控制(SMC)H∞控制相结合的方法应用于永磁直线同步电机(PMLSM)的鲁棒控制。首先阐述了PMLSM的基本数学模型及其物理意义,包括d-q坐标系下的电压方程运动方程。随后解释了滑模控制的工作原理,重点在于如何构建滑模面并确保系统沿此面稳定运行。接着讨论了H∞控制的目标——即使在不确定条件下也能保持良好的性能表现。文中还提供了具体的Matlab代码实例,展示了如何利用Matlab控制系统工具箱进行H∞控制器的设计。最后给出了一段完整的Matlab程序框架,演示了两种控制方法的具体实现方式。 适合人群:从事电机控制领域的研究人员技术人员,尤其是那些想要深入了解滑模控制H∞控制理论及其在实际工程中应用的人士。 使用场景及目标:适用于需要提高永磁直线同步电机控制系统抗干扰能力鲁棒性的场合,如工业自动化生产线、精密加工设备等。通过学习本篇文章提供的理论知识编程技巧,读者能够掌握这两种先进控制策略的应用方法,从而提升自身解决复杂控制问题的能力。 其他说明:文中所涉及的内容不仅限于理论讲解,还包括了大量的实战经验分享,有助于读者快速上手并在实践中不断改进自己的设计方案。同时鼓励读者积极尝试不同的参数配置,以便找到最适合特定应用场景的最佳解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值