学习资源
https://www.cnblogs.com/flashhu/p/8324551.html
中序遍历Splay得到的每个点的深度序列严格递增
时间复杂度 O ( ( n + m ) log n ) O((n+m)\log n) O((n+m)logn)
那些年我用lct做过的题目:
- P3690 【模板】Link Cut Tree (动态树)
- P1501 [国家集训队]Tree II - ( u , v ) (u,v) (u,v) 路径上所有值都加上/ 乘上 c c c
- P2542 [AHOI2005] 航线规划 / NIT19789 mustedge mustedge mustedge - 双连通分量、并查集、缩点
数组版代码
namespace LCT { // lct板子
const ll mod = 51061;
ll sum[N];
ll w[N];// w[x]表示x的点权
int h[N];// 缩点后x所在的点 类似 并查集里的father[]
int sz[N];//sz[x] 表示以x为根的splay树的大小
int rev[N];//rev为区间翻转懒标记数组
int f[N];// 父节点
int st[N];//栈
int c[N][2];// 每个节点的两个儿子
// 判断节点x是否为一个splay的根 如果不是根 返回true
inline bool nRoot(register int x) {
// 如果节点x是根 则其与其父亲节点之间连的是轻边
// 如果连的是轻边 他的父亲的儿子里没有它
return c[f[x]][0] == x || c[f[x]][1] == x;
}
#define lc c[x][0]
#define rc c[x][1]
// splay区间翻转操作
inline void reverse(register int x) {
swap(lc, rc);
rev[x] ^= 1;
}
// 判断并释放懒标记
inline void pushDown(register int x) {
if (rev[x]) {
swap(lc, rc);
rev[lc] ^= 1, rev[rc] ^= 1;
rev[x] = 0;
}
}
// 更新信息
inline void pushUp(register int x) {
sum[x] = (sum[lc] + sum[rc] + w[x]) % mod;
sz[x] = sz[lc] + sz[rc] + 1;
}
// 一次旋转
inline void rotate(register int x) {
register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
if (nRoot(y))
c[z][c[z][1] == y] = x;
c[x][!k] = y;
c[y][k] = w;
if (w)
f[w] = y;
f[y] = x;
f[x] = z;
pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
}
// 旋转一颗splay树 改为以x为根节点
inline void splay(register int x) {
register int y = x, z = 0;
st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
while (nRoot(y)) st[++z] = y = f[y];
while (z) pushDown(st[z--]);
//然后再从下往上旋转 splay的基本操作
while (nRoot(x)) {
y = f[x];
z = f[y];
if (nRoot(y))
rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
rotate(x);
}
pushUp(x);//更新最终新父亲x的信息
}
// 并查集find
inline int geth(register int x) {
return x == h[x] ? x : h[x] = geth(h[x]);
}
// 打通当前根节点到指定节点的树链
// 使得一条中序遍历以根开始、以指定点结束的splay出现
inline void access(register int x) {
//for (int y = 0; x; y = x, x = f[x] = geth(f[x])) { // merge时使用
for (int y = 0; x; x = f[y = x]) {
splay(x);
rc = y;
pushUp(x);
}
}
//换根
inline void makeRoot(register int x) {
access(x);
splay(x);
reverse(x);
}
//提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
inline void split(register int x, register int y) {
makeRoot(x);
access(y);
splay(y);
}
// 查找在树的根
// 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
// 一直往左儿子就是了
int findRoot(register int x) {
access(x);
splay(x);
while (lc) {
pushDown(x);
x = lc;
}
// 现在x是root
splay(x);
return x;
}
// 连边
inline void link(register int x, register int y) {
makeRoot(x);
if (findRoot(y) != x)
f[x] = y;
}
// 断边
inline void cut(register int x, register int y) {
makeRoot(x);
if (findRoot(y) == x && f[y] == x && !c[y][0]) {
f[y] = c[x][1] = 0;
pushUp(x);
}
}
// 判断 x 和 y 是否连通
bool isConnected(int x, int y) {
return findRoot(x) == findRoot(y);
}
// 缩点
inline void del(register int x, register int y) {
if (x) { // 如果点存在
h[x] = y;
del(lc, y);
del(rc, y);
}
}
//双连通分量存在时 连接x和y
inline void merge(register int x, register int y) {
if (x == y) return;
makeRoot(x);
if (findRoot(y) != x) {
f[x] = y;// 等于 link(x,y)
return;
}
// 双连通分量 暴力缩点
del(rc, x);// 将以x为根的splay树缩成一个点 都用x来表示
rc = 0;
pushUp(x);//缩点 删点
}
void init(int n) {
for (int i = 0; i <= n; i++) {
f[i] = c[i][0] = c[i][1] = 0;
w[i] = 1;
h[i] = i;
}
}
}
using namespace LCT;
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;
namespace LCT { // lct板子
int v[N];// 点权
int f[N];// 父节点
int st[N];//栈
int c[N][2];// 每个节点的两个儿子
// 判断节点x是否为一个splay的根 如果不是根 返回true
inline bool nRoot(register int x) {
// 如果节点x是根 则其与其父亲节点之间连的是轻边
// 如果连的是轻边 他的父亲的儿子里没有它
return c[f[x]][0] == x || c[f[x]][1] == x;
}
int rev[N];//rev为区间翻转懒标记数组
#define lc c[x][0]
#define rc c[x][1]
// splay区间翻转操作
inline void reverse(register int x) {
swap(lc,rc);
rev[x] ^= 1;
}
// 判断并释放懒标记
inline void pushDown(register int x) {
if (rev[x]) {
if (lc) reverse(lc);
if (rc) reverse(rc);
rev[x] = 0;
}
}
int a[N];// a[x]表示 以x为根的splay树在中序遍历下形成的区间 的异或总和
// 更新信息
inline void pushUp(register int x) {
a[x] = a[lc] ^ a[rc] ^ v[x];
}
// 一次旋转
inline void rotate(register int x) {
register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
if (nRoot(y))
c[z][c[z][1] == y] = x;
c[x][!k] = y;
c[y][k] = w;
if (w)
f[w] = y;
f[y] = x;
f[x] = z;
pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
}
// 旋转一颗splay树 改为以x为根节点
inline void splay(register int x) {
register int y = x, z = 0;
st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
while (nRoot(y)) st[++z] = y = f[y];
while (z) pushDown(st[z--]);
//然后再从下往上旋转 splay的基本操作
while (nRoot(x)) {
y = f[x];
z = f[y];
if (nRoot(y))
rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
rotate(x);
}
pushUp(x);//更新最终新父亲x的信息
}
// 打通当前根节点到指定节点的树链
// 使得一条中序遍历以根开始、以指定点结束的splay出现
inline void access(register int x) {
for (int y = 0; x; y = x, x = f[x]) {
splay(x);
rc = y;
pushUp(x);
}
}
//换根
inline void makeRoot(register int x) {
access(x);
splay(x);
reverse(x);
}
//提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
inline void split(register int x, register int y) {
makeRoot(x);
access(y);
splay(y);
}
// 查找在树的根
// 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
// 一直往左儿子就是了
int findRoot(register int x) {
access(x);
splay(x);
while (lc) {
pushDown(x);
x = lc;
}
// 现在x是root
splay(x);
return x;
}
// 连边
inline void link(register int x, register int y) {
makeRoot(x);
if (findRoot(y) != x) {
f[x] = y;
};
}
// 断边
inline void cut(register int x, register int y) {
makeRoot(x);
if (findRoot(y) == x && f[y] == x && !c[y][0]) {
f[y] = c[x][1] = 0;
pushUp(x);
}
}
}
using namespace LCT;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m; // n个点 m个操作
for (int i = 1; i <= n; i++) {
cin >> v[i]; // 每个点的点权
}
int op, x, y;
while (m--) {
cin >> op >> x >> y;
if (op == 0) { // 询问 (x->y)所有点权xor和
split(x, y);
//此时y为splay树的根 y是树中中序遍历到的第一个点 x是中序遍历到的最后一个点
printf("%d\n", a[y]);
} else if (op == 1) { //连通x->y
link(x, y);
} else if (op == 2) { // 删除x->y这条边
cut(x, y);
} else {//op=3 将x点权变为y
// 每次修改值的时候 需要将x转上来
splay(x);
v[x] = y;
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m, k;
typedef long long ll ;
namespace LCT { // lct板子
const int mod = 51061;
ll sum[N];// sum[x] 表示x所表示的区间的和
ll add[N], mul[N];// 区间懒标记 表示还未下放的数
ll w[N];// w[x]表示x的点权
int sz[N];//sz[x] 表示以x为根的splay树的大小
int rev[N];//rev为区间翻转懒标记数组
int f[N];// 父节点
int st[N];//栈
int c[N][2];// 每个节点的两个儿子
// 判断节点x是否为一个splay的根 如果不是根 返回true
inline bool nRoot(register int x) {
// 如果节点x是根 则其与其父亲节点之间连的是轻边
// 如果连的是轻边 他的父亲的儿子里没有它
return c[f[x]][0] == x || c[f[x]][1] == x;
}
#define lc c[x][0]
#define rc c[x][1]
// splay区间翻转操作
inline void reverse(register int x) {
swap(lc,rc);
rev[x] ^= 1;
}
// 给x所代表的区间都加上一个值ad
void Add(int x, int ad) {
add[x] = (add[x] + ad) % mod;
sum[x] = (sum[x] + sz[x] * ad % mod) % mod;
w[x] = (w[x] + ad) % mod;
}
// 给x所代表的区间都乘上一个值mu
void Mul(int x, int mu) {
mul[x] = mul[x] * mu % mod;
add[x] = add[x] * mu % mod;
sum[x] = sum[x] * mu % mod;
w[x] = w[x] * mu % mod;
}
// 判断并释放懒标记
inline void pushDown(register int x) {
if (rev[x]) {
if (lc) reverse(lc);
if (rc) reverse(rc);
rev[x] = 0;
}
if (mul[x] != 1) {
if (lc) Mul(lc, mul[x]);
if (rc) Mul(rc, mul[x]);
mul[x] = 1;
}
if (add[x]) {
if (lc) Add(lc, add[x]);
if (rc) Add(rc, add[x]);
add[x] = 0;
}
}
// 更新信息
inline void pushUp(register int x) {
sum[x] = (sum[lc] + sum[rc] + w[x]) % mod;
sz[x] = sz[lc] + sz[rc] + 1;
}
// 一次旋转
inline void rotate(register int x) {
register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
if (nRoot(y))
c[z][c[z][1] == y] = x;
c[x][!k] = y;
c[y][k] = w;
if (w)
f[w] = y;
f[y] = x;
f[x] = z;
pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
}
// 旋转一颗splay树 改为以x为根节点
inline void splay(register int x) {
register int y = x, z = 0;
st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
while (nRoot(y)) st[++z] = y = f[y];
while (z) pushDown(st[z--]);
//然后再从下往上旋转 splay的基本操作
while (nRoot(x)) {
y = f[x];
z = f[y];
if (nRoot(y))
rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
rotate(x);
}
pushUp(x);//更新最终新父亲x的信息
}
// 打通当前根节点到指定节点的树链
// 使得一条中序遍历以根开始、以指定点结束的splay出现
inline void access(register int x) {
for (int y = 0; x; x = f[y = x]) {
splay(x);
rc = y;
pushUp(x);
}
}
//换根
inline void makeRoot(register int x) {
access(x);
splay(x);
reverse(x);
}
//提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
inline void split(register int x, register int y) {
makeRoot(x);
access(y);
splay(y);
}
// 查找在树的根
// 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
// 一直往左儿子就是了
int findRoot(register int x) {
access(x);
splay(x);
while (lc) {
pushDown(x);
x = lc;
}
// 现在x是root
splay(x);
return x;
}
// 连边
inline void link(register int x, register int y) {
makeRoot(x);
if (findRoot(y) != x)
f[x] = y;
}
// 断边
inline void cut(register int x, register int y) {
makeRoot(x);
if (findRoot(y) == x && f[y] == x && !c[y][0]) {
f[y] = c[x][1] = 0;
pushUp(x);
}
}
}
using namespace LCT;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int u, v, c, q;
cin >> n >> q;
for (int i = 1; i <= n; i++) {
w[i] = mul[i] = 1;
}
for (int i = 1; i < n; i++) {
cin >> u >> v;
link(u, v);
}
string op;
while (q--) {
cin >> op;
if (op == "+") { // 将 u 到 v 的路径上的点的权值都加上自然数 c
cin >> u >> v >> c;
split(u, v);
Add(v, c);
} else if (op == "-") { //将树中原有边(u1,v1)删除 加入新的边(u2,v2)
cin >> u >> v;
cut(u, v);
cin >> u >> v;
link(u, v);
} else if (op == "*") {//将 u 到 v 的路径上的点的权值都乘上自然数 c
cin >> u >> v >> c;
split(u, v);
Mul(v, c);
} else { //询问 u 到 v 的路径上的点的权值和 将答案对 51061 取模
cin >> u >> v;
split(u, v);
cout << sum[v] << endl;
}
}
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
namespace LCT { // lct板子
int h[N];// 缩点后x所在的点
int w[N];// w[x]表示x的点权
int rev[N];//rev为区间翻转懒标记数组
int f[N];// 父节点
int st[N];//栈
int c[N][2];// 每个节点的两个儿子
// 判断节点x是否为一个splay的根 如果不是根 返回true
inline bool nRoot(register int x) {
// 如果节点x是根 则其与其父亲节点之间连的是轻边
// 如果连的是轻边 他的父亲的儿子里没有它
return c[f[x]][0] == x || c[f[x]][1] == x;
}
#define lc c[x][0]
#define rc c[x][1]
// splay区间翻转操作
inline void reverse(register int x) {
swap(lc, rc);
rev[x] ^= 1;
}
// 判断并释放懒标记
inline void pushDown(register int x) {
if (rev[x]) {
swap(lc, rc);
rev[lc] ^= 1, rev[rc] ^= 1;
rev[x] = 0;
}
}
// 更新信息
inline void pushUp(register int x) {
w[x] = w[lc] + w[rc] + 1;
}
// 一次旋转
inline void rotate(register int x) {
register int y = f[x], z = f[y], k = (c[y][1] == x), w = c[x][!k];
if (nRoot(y))
c[z][c[z][1] == y] = x;
c[x][!k] = y;
c[y][k] = w;
if (w)
f[w] = y;
f[y] = x;
f[x] = z;
pushUp(y);//原来的父亲现在是儿子了 更新新儿子的信息 而新父亲的信息不是在这里更新的
}
// 旋转一颗splay树 改为以x为根节点
inline void splay(register int x) {
register int y = x, z = 0;
st[++z] = y;//暂存当前点到根的整条路径 pushdown时一定要从上往下放标记
while (nRoot(y)) st[++z] = y = f[y];
while (z) pushDown(st[z--]);
//然后再从下往上旋转 splay的基本操作
while (nRoot(x)) {
y = f[x];
z = f[y];
if (nRoot(y))
rotate((c[y][0] == x) ^ (c[z][0] == y) ? x : y);
rotate(x);
}
pushUp(x);//更新最终新父亲x的信息
}
inline int geth(register int x) {
return x == h[x] ? x : h[x] = geth(h[x]);
}
// 打通当前根节点到指定节点的树链
// 使得一条中序遍历以根开始、以指定点结束的splay出现
inline void access(register int x) {
for (int y = 0; x; y = x, x = f[x] = geth(f[x])) {
splay(x);
rc = y;
pushUp(x);
}
}
//换根
inline void makeRoot(register int x) {
access(x);
splay(x);
//reverse(x);
rev[x] ^= 1;
}
//提取路径 拉出x->y的路径成为一个splay 然后以y为splay的根
inline void split(register int x, register int y) {
makeRoot(x);
access(y);
splay(y);
}
// 查找在树的根
// 因为lct要求满足中序遍历Splay得到的每个点的深度序列严格递增 根的深度最小
// 一直往左儿子就是了
int findRoot(register int x) {
access(x);
splay(x);
while (lc) {
pushDown(x);
x = lc;
}
// 现在x是root
splay(x);
return x;
}
// 连边
inline void link(register int x, register int y) {
makeRoot(x);
if (findRoot(y) != x) {
f[x] = y;
}
}
// 断边
inline void cut(register int x, register int y) {
makeRoot(x);
if (findRoot(y) == x && f[y] == x && !c[y][0]) {
f[y] = c[x][1] = 0;
pushUp(x);
}
}
inline void del(register int x, register int y) {
if (x) {
h[x] = y;
del(lc, y);
del(rc, y);
}
}
//双连通分量存在时 连接x和y
inline void merge(register int x, register int y) {
if (x == y) return;
makeRoot(x);
if (findRoot(y) != x) {
f[x] = y;// 等于 link(x,y)
return;
}
// 双连通分量 暴力缩点
del(rc, x);// 将以x为根的splay树缩成一个点 都用x来表示
rc = 0;
pushUp(x);//缩点 删点
}
}
using namespace LCT;
struct Edge {
int u, v;
bool operator<(const Edge b) const {
return u < b.u || (u == b.u && v < b.v);
}
} e[N];
struct Operation {
int u, v, op;
} a[N];
int vis[N], res[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int u, v;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
w[i] = 1;// 点权
h[i] = i;// 类似并查集里的f[]
}
for (int i = 1; i <= m; i++) {
cin >> u >> v;
if (u > v) swap(u, v);
e[i] = {u, v};
}
sort(e + 1, e + 1 + m);
int op, id = 0;
while (cin >> op && op != -1) {
cin >> u >> v;
// op=1 询问 u->v的关键路线
// op=0 断边
if (!op) {
if (u > v) swap(u, v);
vis[lower_bound(e + 1, e + 1 + m, (Edge) {u, v}) - e] = 1;
}
a[++id] = {u, v, op};
}
// 把没有断边的点 全部连起来
for (int i = 1; i <= m; i++) {
if (!vis[i]) {
merge(geth(e[i].u), geth(e[i].v));
}
}
int cnt = 0;
for (int i = id; i; i--) {
u = geth(a[i].u);
v = geth(a[i].v);
if (a[i].op) {
split(u, v);
res[++cnt] = w[v] - 1;
} else {
merge(u, v);
}
}
for (int i = cnt; i; i--) {
cout << res[i] << endl;
}
return 0;
}