LCT的讲解 wikipedia 说的挺清楚的LCT——wikipedia
LCT的重点好像还是不到十行,就是一个access操作.
access实际上干的事情呢, 就是把这个点到根的这条路打通(都成为实(重)边)。并且用一棵二叉排序树来维护这条路(一个点属于这棵二叉排序树当且仅当它是那个点的祖先)。另外, 这棵树排序的标准是一个点的左儿子的深度小于自己。
这幅图就是access操作了。
实现的时候呢, 就从x 到root把这条链建出来就好了。如果我们已经建好了从x到 i 的这条链, 也就是说下一步加上 i 到 i 的父亲这一条链,这时候我们只要把 i splay到它所在的这棵树的最顶端, 然后断开它和右儿子的链接(因为它下面那条链是要连着i的, 不能连其它的点), 然后 再把 i 作为 这个右儿子就可以了, 还是很显然的。
写代码时也不用考虑断开接上什么的, 因为 fat 这个数组, 可以有两种含义, 当它是当前树的根的时候, fat连的是原图中的父节点(也就是上一棵树中深度比当前树种深度最小的点小一的那个点), 而当它不是当前树的根的时候, fat连的就是 它在二叉排序树种的父亲(如果这个节点是左儿子, 那么连的就是它在原图中的儿子, 否则连的是它在原图中的父亲)。 所以在进行所谓的断开或者接上操作后, fat这个数组是没有发生变化的, 唯一变化的就是当前这个节点还是不是当前子树的根 (当然这个变化也可以没有因为如果不开一个bool的数组标记root的时候也可以直接看一看它的fat的两个child是不是都不等于自己, 如果都不等于的话它就一定是root了), 所以开一下child数组和root数组就好了。
void access(int x){
int y = 0;
do{
splay(x);
rt[ch[x][1]] = 1;
rt[ch[x][1] = y] = 0;
pushup(x);
x = fat[y = x];
}while(x);
}
UPD :
现在更喜欢这样写access:这样写就可以少一个 求 LCA 的函数啦!inline int access(int x){
int y;
for(y = 0; x; x = fat[y = x]){
splay(x);
rt[ch[x][1]] = 1;
rt[ch[x][1] = y] = 0;
pushup(x);
} return y;
}
简单题:
bzoj 2002 弹飞绵羊(这个名字真的好可爱啊我受不了了。。越想越可爱n(*≧▽≦*)n)
这道题呢, 只需要维护原树中到root的距离, 所以把它access一下再splay一下看一下左儿子有多大就可以解决了。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 200005
using namespace std;
int n, fat[MAXN], ch[MAXN][2], sz[MAXN];
bool root[MAXN];
inline void pushup(int x){sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
void rotate(int x, int k){
int y = fat[x];jie(ch[x][k], y, !k);
fat[x] = fat[y]; jie(y, x, k);
if(root[y])root[y] = 0, root[x] = 1;
else ch[fat[x]][ch[fat[x]][1] == y] = x;
pushup(y);
}
void splay(int x){
while(!root[x]){
int y = fat[x], ky = ch[fat[y]][1] == y;
if(root[y]){rotate(x, ch[y][0] == x); break;}
if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
else rotate(x, ky), rotate(x, !ky);
}pushup(x);
}
void access(int x){
int y = 0;
do{
splay(x);
root[ch[x][1]] = 1;
root[ch[x][1] = y] = 0;
pushup(x);
x = fat[y = x];
}while(x);
}
int main(){
scanf("%d", &n);int xx, aa, bb, kk;
memset(root, true, sizeof(root));
for(int i = 1; i <= n + 1; i ++)sz[i] = 1;
for(int i = 1; i <= n; i ++){
scanf("%d", &xx); fat[i] = (i + xx > n) ? n + 1: xx + i;
}int q; scanf("%d", &q); while(q --){
scanf("%d%d", &kk, &aa); aa ++;
if(kk == 1){
access(aa); splay(aa);
printf("%d\n", sz[ch[aa][0]]);
}else{ scanf("%d", &bb);
access(aa); splay(aa);
fat[ch[aa][0]] = 0;
root[ch[aa][0]] = 1;
ch[aa][0] = 0;
sz[aa] = sz[ch[aa][1]] + 1;
fat[aa] = (aa + bb > n) ? n + 1: bb + aa;
}
} //system("pause");
return 0;
}
spoj 375 QTREE
一道裸的树剖题, 但是多了一个 询问lca的操作。
其实询问lca的操作也很简单。 考虑把一个点进行了 access 操作, 这时候如果 要求它和另一个点的lca 的话, 它们的lca 存在 且只存在 从 这个点到根的这条链上, 即这个点存在的(也是root存在的)那棵splay上, 所以我们只需要把另一个点一层一层地叠上去看一下 它是否在这棵树上就可以了。当然在叠上去的时候也是要靠splay 来做的, 每次把它splay一下就可以了, 直到splay了之后它的父亲是 0, 也就是它在 那条链上了。
综上, lca的代码写起来和 access 的基本上一样。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 10005
using namespace std;
int n, ee, head[MAXN], fat[MAXN], ch[MAXN][2], key[MAXN], ti[MAXN], maxx[MAXN];
bool rt[MAXN];
struct Edge{
int to, next, c, idd;
}edge[MAXN * 2];
inline void addedge(int s, int y, int c, int dd){
edge[++ ee].to = y;
edge[ee].next = head[s];
edge[ee].idd = dd;
edge[ee].c = c;
head[s] = ee;
}
void dfs(int x){
for(int i = head[x]; i != -1; i = edge[i].next)if(fat[edge[i].to] == 0){
key[edge[i].to] = edge[i].c,
fat[edge[i].to] = x,
ti[edge[i].idd] = edge[i].to,
dfs(edge[i].to);
}
}
inline void pushup(int x){maxx[x] = max(max(maxx[ch[x][0]], maxx[ch[x][1]]), key[x]);}
inline void jie(int x, int y, int k){fat[x] = y; ch[y][k] = x;}
inline void rotate(int x, int k){
int y = fat[x];
jie(ch[x][k], y, !k);
fat[x] = fat[y];
jie(y, x, k);
if(rt[y])rt[y] = 0, rt[x] = 1;
else ch[fat[x]][ch[fat[x]][1] == y] = x;
pushup(y);
}
void splay(int x){
while(!rt[x]){
int y = fat[x], ky = ch[fat[y]][1] == y;
if(rt[y]){rotate(x, ch[y][0] == x); break;}
if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
else rotate(x, ky), rotate(x, !ky);
} pushup(x);
}
void access(int x){
int y = 0;
do{
splay(x);
rt[ch[x][1]] = 1;
rt[ch[x][1] = y] = 0;
pushup(x);
x = fat[y = x];
}while(x);
}
void change(int x, int c){//cout<<x<<endl;
access(x);
key[x] = c;
pushup(x);
}
void query(int u, int v){
access(v), v = 0;
while(u){
splay(u);//cout<<u<<endl;
if(!fat[u]){printf("%d\n", max(maxx[v], maxx[ch[u][1]])); return;}
rt[ch[u][1]] = 1;
rt[ch[u][1] = v] = 0;
pushup(u);
u = fat[v = u];
}
}
int main(){
int t; scanf("%d", &t); while(t --){
scanf("%d", &n); ee = 0;
memset(rt, 1, sizeof(rt));
memset(ch, 0, sizeof(ch));
memset(fat, 0, sizeof(fat));
memset(head, -1, sizeof(head));
maxx[0] = -(1<<30);
for(int i = 1; i < n; i ++){
int aa, bb, cc; scanf("%d%d%d", &aa, &bb, &cc);
addedge(aa, bb, cc, i), addedge(bb, aa, cc, i);
} fat[1] = -1; dfs(1); fat[1] = 0;
char s[20], aa, bb;
while(scanf("%s", s)){if(s[0] == 'D')break;
int aa, bb; scanf("%d%d", &aa, &bb);
if(s[0] == 'C')change(ti[aa], bb);
else query(aa, bb);
}
}
return 0;
}
bzoj 2049 洞穴勘测
第一道真正的link / cut tree
前面那两道都只能算是splay 启发式合并吧,并没有真正地进行 link 和cut 的操作。
这道题就是一个带有link cut的裸题。
link 的时候, 最直接的想法就是把一个 点的 fat 设为另一个点, 但是每个点 都已经有fat了, 没法设。 唯一没有fat的点就是 整棵辅助树的root节点(并不是一棵splay的root节点, 那个节点也有一个连着上一棵splay的虚边), 所以说这要把一个点设为 总体的root, 然后把它的fat设为另一个点就可以了。
cut的时候, 直接的想法是要判断一下两个节点哪个更靠近当前这棵splay的根, 然后把它们之间的那条边断掉。 但是看在判断哪个靠近根还要再写一个函数, 我们可以先把X设为根, 然后在access y 并splay y, 使得这时候y在根, 然后这时很显然x , 就是y 的左儿子。 所以断掉y的左儿子就可以了。
代码只有70行, 而且还有60行是上一题也写了的, 但是我狂RE 了三天, 每天早上敲一遍这题也真是醉了, 最后发现我就是在cut 完了以后忘了把cut下来的那个点的root设为1了!!! ~~~~(>_<)~~~~
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define MAXN 10005
using namespace std;
int n, m, fat[MAXN], ch[MAXN][2];
bool rev[MAXN], root[MAXN];
void reverse(int x){if(!x)return;
rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void pushdown(int x){if(!rev[x])return;
rev[x] = 0; reverse(ch[x][0]); reverse(ch[x][1]);
}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
inline void rotate(int x, int k){
int y = fat[x];
jie(ch[x][k], y, !k);
fat[x] = fat[y];
jie(y, x, k);
if(root[y])root[y] = 0, root[x] = 1;
else ch[fat[x]][ch[fat[x]][1] == y] = x;
}
void Pushdown(int x){
if(!root[x])Pushdown(fat[x]);
pushdown(x);
}
inline void splay(int x){
Pushdown(x);
while(!root[x]){
int y = fat[x], ky = ch[fat[y]][1] == y;
if(root[y]){rotate(x, ch[y][0] == x);break;}
if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
else rotate(x, ky), rotate(x, !ky);
}
}
void access(int x){
int y = 0;
do{
splay(x);
root[ch[x][1]] = 1;
root[ch[x][1] = y] = 0;
x = fat[y = x];
}while(x);
}
bool ask(int x, int y){
while(fat[x])x = fat[x];
while(fat[y])y = fat[y];
return x == y;
}
bool makeroot(int x){
access(x), splay(x), reverse(x);
}
void link(int x, int y){
makeroot(x); fat[x] = y;
}
void cut(int x, int y){
makeroot(x); access(y); splay(y); ch[y][0] = fat[x] = 0; root[x] = 1;
}
int main(){
scanf("%d%d", &n, &m);
memset(root, 1, sizeof(root));
while(m --){ char s[20]; int x, y;
scanf("%s%d%d", s, &x, &y);
if(s[0] == 'C')link(x, y);
if(s[0] == 'D')cut(x, y);
if(s[0] == 'Q')ask(x, y) ? puts("Yes") : puts("No");
}
return 0;
}
bzoj 3282
最简单的LCT, 可喜可贺的是我没有调试, 让人一下子产生了LCT真的好简单啊的想cuo法jue
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdio>
#define MAXN 300005
using namespace std;
int n, m, val[MAXN], fat[MAXN], ch[MAXN][2], s[MAXN];
bool rev[MAXN], root[MAXN];
inline void pushup(int x){s[x] = val[x] ^ s[ch[x][0]] ^ s[ch[x][1]];}
inline void reverse(int x){
rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void pushdown(int x){if(!rev[x])return;
rev[x] = 0; reverse(ch[x][0]); reverse(ch[x][1]);
}
inline void jie(int x, int y, int k){ch[y][k] = x; fat[x] = y;}
inline void rotate(int x, int k){
int y = fat[x]; pushdown(y);
jie(ch[x][k], y, !k);
fat[x] = fat[y];
jie(y, x, k);
if(root[y])root[y] = 0, root[x] = 1;
else ch[fat[x]][ch[fat[x]][1] == y] = x;
pushup(y);
}
inline void Pushdown(int x){
if(!root[x])Pushdown(fat[x]);
pushdown(x);
}
inline void splay(int x){
Pushdown(x);
while(!root[x]){
int y = fat[x], ky = ch[fat[y]][1] == y;
if(root[y]){rotate(x, ch[y][0] == x); break;}
if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
else rotate(x, ky), rotate(x, !ky);
} pushup(x);
}
inline void access(int x){
int y = 0;
do{
splay(x);
root[ch[x][1]] = 1;
root[ch[x][1] = y] = 0;
pushup(x);
x = fat[y = x];
}while(x);
}
bool same(int x, int y){
while(fat[x])x = fat[x];
while(fat[y])y = fat[y]; return x == y;
}
bool makeroot(int x){
access(x), splay(x), reverse(x);
}
inline void link(int x, int y){
makeroot(x); fat[x] = y;
}
inline void cut(int x, int y){
makeroot(x); access(y); splay(y); ch[y][0] = fat[x] = 0; root[x] = 1;
}
int main(){
scanf("%d%d", &n, &m);
memset(root, 1, sizeof(root));
for(int i = 1; i <= n; i ++)scanf("%d", &val[i]);
while(m --){
int k, a, b; scanf("%d%d%d", &k, &a, &b);
if(k == 0){makeroot(a); access(b); splay(b); printf("%d\n", s[b]);}
if(k == 1){if(!same(a, b)) link(a, b);}
if(k == 2){if(same(a, b)) cut(a, b);}
if(k == 3){access(a); splay(a); val[a] = b; pushup(a);}
}
// system("pause");
return 0;
}
bzoj 2631
又是一道裸LCT >_<
现在觉得拍这种题真是爽啊,,不用思考
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define MAXN 100005
#define mod 51061
using namespace std;
int n, m, jia[MAXN], cheng[MAXN], sz[MAXN], sum[MAXN], val[MAXN], ch[MAXN][2], fat[MAXN];
bool root[MAXN], rev[MAXN];
inline void reverse(int x){
rev[x] ^= 1; swap(ch[x][0], ch[x][1]);
}
inline void calc(int x, int c, int j){
if(!x)return;
val[x] = (((long long)val[x] * c) + j) % mod;
sum[x] = ((long long)sum[x] * c + (long long)sz[x] * j) % mod;
cheng[x] = ((long long)cheng[x] * c) % mod;
jia[x] = ((long long)jia[x] * c + j) % mod;
}
inline void pushdown(int x){
if(rev[x]){
rev[x] = 0;
reverse(ch[x][0]), reverse(ch[x][1]);
}
if(cheng[x] != 1 || jia[x]){
calc(ch[x][0], cheng[x], jia[x]), calc(ch[x][1], cheng[x], jia[x]);
cheng[x] = 1, jia[x] = 0;
}
}
inline void pushup(int x){
sum[x] = (sum[ch[x][0]] + sum[ch[x][1]] + val[x]) % mod;
sz[x] = (sz[ch[x][0]] + sz[ch[x][1]] + 1) % mod;
}
inline void jie(int x, int y, int k){fat[x] = y; ch[y][k] = x;}
inline void rotate(int x, int k){
int y = fat[x]; pushdown(y);
jie(ch[x][k], y, !k);
fat[x] = fat[y];
jie(y, x, k);
if(root[y])root[y] = 0, root[x] = 1;
else ch[fat[x]][ch[fat[x]][1] == y] = x;
pushup(y);
}
inline void Pushdown(int x){
if(!root[x])Pushdown(fat[x]);
pushdown(x);
}
inline void splay(int x){
Pushdown(x);
while(!root[x]){
int y = fat[x], ky = ch[fat[y]][1] == y;
if(root[y]){rotate(x, ch[y][0] == x) ; break;}
if(ch[y][ky] == x)rotate(y, !ky), rotate(x, !ky);
else rotate(x, ky), rotate(x, !ky);
}pushup(x);
}
void access(int x){
int y = 0;
do{
splay(x);
root[ch[x][1]] = 1;
root[ch[x][1] = y] = 0;
pushup(x);
x = fat[y = x];
}while(x);
}
inline void makeroot(int x){
access(x), splay(x), reverse(x);
}
inline void link(int x, int y){
makeroot(x); fat[x] = y;
}
inline void cut(int x, int y){
makeroot(x); access(y), splay(y); fat[x] = ch[y][0] = 0; root[x] = 1;
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i ++)root[i] = val[i] = sum[i] = sz[i] = cheng[i] = 1;
for(int i = 1; i < n; i ++){
int u, v; scanf("%d%d", &u, &v); link(u, v);
}while(m --){
char s[20]; int a, b, c, d;scanf("%s%d%d", s, &a, &b);
if(s[0] == '-'){
scanf("%d%d", &c, &d);
cut(a, b); link(c, d);
}
if(s[0] == '/'){
makeroot(a); access(b), splay(b);
printf("%d\n", sum[b]);
}
if(s[0] == '+'){
scanf("%d", &c);
makeroot(a); access(b), splay(b);
calc(b, 1, c);
}
if(s[0] == '*'){
scanf("%d", &c);
makeroot(a); access(b), splay(b);
calc(b, c, 0);//cout<<sum[3]<<endl;
}
}
return 0;
}