动态树问题与Link-Cut Trees学习笔记
动态树问题
维护一个动态森林,支持:
Link x, y
将x和y连接Cut x, y
删除x与y之间的边Query x, y
询问x和y是否在一棵树内
Link_Cut Trees
作为Tarjan神犇研究的玩意,命名和Union-Find Set如出一辙…
具体描述见论文《QTree解法的一些研究,yangzhe,2007》,这里对一些容易引发误解的地方做出说明。
基本结构
就是splay的森林,u,v在同一棵splay内当且仅当他们在原树中位于同一条偏爱路径。(splay中)u在v左边当且仅当在原树中u在v上方。 不难证明森林和这里的“splay森林”构成一一对应的关系。由于splay的旋转不会改变splay中的左右关系,因而对应的splay森林不变,由于“一一对应”,原森林也不变。
操作
- access(u):暴力将当前节点到根的路径变为偏爱路径
make_rt(u):换根。将u设为其所在树的根。
Make-rt(u) access u splay u to the root reverse the path which is u belonged
这也就是让我(貌似还有很多人)迷惑的rev数组的由来,其作用就是用lazytag的思想翻转一条链。
link(i, j):连接i, j
Link(i, j) Make-rt i fa[i] = j
cut(i, j):切断i和j间的路径
Cut(i, j) Make-rt i access j splay j fa[i] = chl[j][0] = 0
这里换根将两种情况同一考虑了。
cut-fa(i): 切断i和父亲的路径
Cut-fa(i) access i splay i fa[chl[i][0]] = 0 chl[i][0] = 0
对于维护有根树的情景,由于不能随意变动根,要采取这样的方法。
时间复杂度和代码复杂度
用一些妙不可言的方法可以证明所有操作都是摊还
代码总的来说还算清晰,和链剖复杂度类似,不过用起来比链剖要灵活的多了。用yangzhe神犇的话说,就是 “不在意局部的平衡而关注全局平衡” 带来的优势。
几个模板题源码
SDOI Cave 洞穴勘测
DS脑残系列1,闹不懂rev啥意思导致2hDebug…
#include <bits/stdc++.h>
using namespace std;
const int maxn = 10005;
struct Link_Cut_Tree {
int fa[maxn], chl[maxn][2], rev[maxn];
int stk[maxn], top;
bool is_root(int nd)
{ return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
void push_down(int nd)
{
if (!rev[nd]) return;
int &lc = chl[nd][0], &rc = chl[nd][1];
if (lc) rev[lc] ^= 1;
if (rc) rev[rc] ^= 1;
rev[nd] = 0;
swap(lc, rc);
}
void zig(int nd)
{
int p = fa[nd], g = fa[p];
int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
if (!is_root(p)) chl[g][tg] = nd;
fa[son] = p, fa[nd] = g, fa[p] = nd;
chl[p][tp] = son, chl[nd][tp^1] = p;
}
void splay(int nd)
{
stk[top = 1] = nd;
for (int i = nd; !is_root(i); i = fa[i]) stk[++top] = fa[i];
while (top) push_down(stk[top--]);
while (!is_root(nd)) {
int p = fa[nd], g = fa[p];
int tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (is_root(p)) {zig(nd); break;}
else if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
}
void access(int x)
{
for (int y = 0; x; x = fa[y = x])
splay(x), chl[x][1] = y;
}
inline void make_rt(int nd)
{ access(nd); splay(nd); rev[nd] ^= 1; }
void link(int i, int j)
{
make_rt(i); fa[i] = j;
access(i);
}
void cut(int i, int j)
{
make_rt(i); access(j); splay(j);
fa[i] = chl[j][0] = 0;
}
int find(int i)
{
access(i); splay(i);
while (chl[i][0]) i = chl[i][0];
return i;
}
}lct;
char s[20];
int n, m;
int u, v;
int main()
{
freopen("sdoi2008_cave.in", "r", stdin);
freopen("sdoi2008_cave2.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
scanf("%s%d%d", s, &u, &v);
if (s[0] == 'C') lct.link(u, v);
else if (s[0] == 'D') lct.cut(u, v);
else {
if (lct.find(u) == lct.find(v)) puts("Yes");
else puts("No");
}
}
return 0;
}
ZJOI Count 树的统计
DS脑残系列2, rev[nd]误作rev导致1.5h的debug…
#include <bits/stdc++.h>
using namespace std;
const int maxn = 30005;
int stk[maxn], top;
int chl[maxn][2], fa[maxn], mx[maxn], sum[maxn], rev[maxn], rt[maxn];
int n, m;
char s[20];
inline bool isrt(int nd)
{ return chl[fa[nd]][0] != nd && chl[fa[nd]][1] != nd; }
void pdw(int nd)
{
if (!rev[nd]) return;
int &lc = chl[nd][0], &rc = chl[nd][1];
if (lc) rev[lc] ^= 1;
if (rc) rev[rc] ^= 1;
rev[nd] = 0, swap(lc, rc);
}
void dfs()
{
for (int nd = 1; nd <= n; nd++)
printf("%d -- %d(%d,%d), sum = %d, mx = %d, rev = %d\n", nd, fa[nd], chl[nd][0], chl[nd][1], sum[nd], mx[nd], rev[nd]);
puts("---");
}
inline void update(int nd)
{
sum[nd] = sum[chl[nd][0]] + sum[chl[nd][1]] + rt[nd];
mx[nd] = max(max(mx[chl[nd][0]], mx[chl[nd][1]]), rt[nd]);
}
void zig(int nd)
{
int p = fa[nd], g = fa[p];
int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
if (!isrt(p)) chl[g][tg] = nd;
fa[nd] = g, fa[p] = nd, fa[son] = p;
chl[nd][tp^1] = p, chl[p][tp] = son;
update(p), update(nd);
}
void splay(int nd)
{
stk[top = 1] = nd;
for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
for (; top; top--)pdw(stk[top]);
while (!isrt(nd)) {
int p = fa[nd], g = fa[p];
int tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (isrt(p)) { zig(nd); break; }
else if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
}
void access(int x)
{
for (int y = 0; x; x = fa[y = x])
splay(x), chl[x][1] = y, update(x);
}
void mkt(int nd)
{ access(nd); splay(nd); rev[nd] ^= 1; }
void link(int u, int v)
{ mkt(u); fa[u] = v; }// 连接
void split(int u, int v)
{ mkt(u);access(v); splay(v); } // 提取区间
int x[maxn], y[maxn];
int main()
{
sum[0] = 0, mx[0] = INT_MIN;
scanf("%d", &n);
int u, v;
for (int i = 1; i < n; i++)
scanf("%d%d", &x[i], &y[i]);
for (int i = 1; i <= n; i++) scanf("%d", &rt[i]), sum[i] = mx[i] = rt[i];
for (int i = 1; i < n; i++) link(x[i], y[i]);
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
scanf("%s%d%d", s, &u, &v);
if (s[1] == 'H') {
splay(u);
rt[u] = v;
update(u);
} else if (s[1] == 'M') split(u, v), printf("%d\n", mx[v]);
else if (s[1] == 'S') split(u, v), printf("%d\n", sum[v]);
}
return 0;
}
HNOI BOUNCE 弹飞绵羊
DS脑残系列3, 维护有根树贸然换根导致1.5hDebug……
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005;
int chl[maxn][2], fa[maxn], rev[maxn], siz[maxn];
int n, m;
int stk[maxn], top = 0;
inline void update(int nd)
{ siz[nd] = siz[chl[nd][0]]+siz[chl[nd][1]]+1;
//cout << nd << "--" << siz[nd] << " " << siz[chl[nd][0]] << " " << siz[chl[nd][1]] << endl;
}
inline bool isrt(int nd)
{ return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
void pdw(int nd)
{
if (!rev[nd]) return;
int &lc = chl[nd][0], &rc = chl[nd][1];
if (lc) rev[lc] ^= 1;
if (rc) rev[rc] ^= 1;
rev[nd] = 0;
swap(lc, rc);
}
void zig(int nd)
{
int p = fa[nd], g = fa[p];
int tp = chl[p][0] != nd, tg = chl[g][0] != p, son = chl[nd][tp^1];
if (!isrt(p)) chl[g][tg] = nd;
fa[nd] = g, fa[p] = nd, fa[son] = p;
chl[nd][tp^1] = p, chl[p][tp] = son;
update(p); update(nd);
}
void splay(int nd)
{
stk[top = 1] = nd;
for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
for (; top; top--) pdw(stk[top]);
while (!isrt(nd)) {
int p = fa[nd], g = fa[p];
if (isrt(p)) { zig(nd); break; }
int tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
}
void access(int x)
{
for (int y = 0; x; x = fa[y = x])
splay(x), chl[x][1] = y, update(x);
}
void mkt(int x)
{ access(x); splay(x); rev[x] ^= 1;
}
void link(int x, int y)
{
//cout << "link " << x << " " << y << endl;
mkt(x);
fa[chl[x][0]] = 0;
chl[x][0] = 0;
fa[x] = y;
update(x);
//access(x);
}
int ask(int nd)
{
access(nd); splay(nd);
return siz[chl[nd][0]]+1;
}
void dfs(int nd, int tab = 0)
{
if (!nd) return;
for (int i = 1; i <= tab; i++) putchar(' ');
printf("%d--%d, siz = %d\n", nd, fa[nd], siz[nd]);
dfs(chl[nd][0], tab+2);
dfs(chl[nd][1], tab+2);
}
int ki[maxn];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++) siz[i] = 1;
for (int i = 1; i <= n; i++) {
scanf("%d", &ki[i]);
if (ki[i] && i+ki[i] <= n) link(i, i+ki[i]);
/// for (int i = 1; i <= n; i++)
/// printf("%d-%d(%d, %d), siz = %d; ", i, fa[i], chl[i][0], chl[i][1], siz[i]);
/// puts("");
}
scanf("%d", &m);
for (int i = 1; i <= m; i++) {
int opt, u, v;
scanf("%d", &opt);
if (opt == 1) {
scanf("%d", &u); u++;
printf("%d\n", ask(u));
} else if (opt == 2){
scanf("%d%d", &u, &v);u++;
ki[u] = v;
if (ki[u] && u+ki[u] <= n) link(u, u+ki[u]);
else link(u, 0);
} else {
scanf("%d", &u); u++;
access(u); splay(u);
dfs(u);
}
}
return 0;
}
NOI2014 魔法森林
第一次LCT 1A!
不过还是因为把nd写成maxn调试了半天…以后MAXN一定用大写…
思路就是用lct维护mst,将边按a排序,然后逐个尝试加入。如果成环就删除环上最大的节点。时间复杂度为不要脸的动态加边spfa高到不知哪里去。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 200005;
int chl[maxn][2], fa[maxn], mx[maxn], rev[maxn], tp = 0; // 拆边用节点
int rt[maxn];
int stk[maxn], top = 0;
inline bool isrt(int nd)
{ return nd != chl[fa[nd]][0] && nd != chl[fa[nd]][1]; }
void pdw(int nd)
{
if (!rev[nd]) return;
int &lc = chl[nd][0], &rc = chl[nd][1];
if (lc) rev[lc] ^= 1; if (rc) rev[rc] ^= 1;
rev[nd] = 0; swap(lc, rc);
}
void dfs()
{
for (int i = 1; i <= tp; i++)
printf("%d-%d(%d, %d), rt = %d, mx = %d, rev = %d\n", i, fa[i], chl[i][0], chl[i][1], rt[i], mx[i], rev[i]);
puts("...........");
}
void update(int nd)
{
//cout << "Updating : " << nd << endl;
int lc = chl[nd][0], rc = chl[nd][1];
if (rt[mx[lc]] > rt[mx[rc]]) mx[nd] = mx[lc];
else mx[nd] = mx[rc];
if (rt[nd] > rt[mx[nd]]) mx[nd] = nd;
}
void zig(int nd)
{
int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, son = chl[nd][tp^1];
if (!isrt(p)) chl[g][chl[g][0]!=p] = nd;
fa[son] = p, fa[p] = nd, fa[nd] = g;
chl[p][tp] = son, chl[nd][tp^1] = p;
update(p), update(nd);
}
void splay(int nd)
{
// cout << "Splaying : " << nd << endl;
stk[top = 1] = nd;
for (int i = nd; !isrt(i); i = fa[i]) stk[++top] = fa[i];
while (top) pdw(stk[top--]);
while (!isrt(nd)) {
int p = fa[nd], g = fa[p], tp = chl[p][0] != nd, tg = chl[g][0] != p;
if (isrt(p)) { zig(nd); break; }
if (tp == tg) zig(p), zig(nd);
else zig(nd), zig(nd);
}
// dfs();
}
void access(int nd)
{
for (int i = 0; nd; nd = fa[i = nd])
splay(nd), chl[nd][1] = i, update(nd);
}
void make_rt(int nd)
{ access(nd); splay(nd); rev[nd] ^= 1; }
void link(int i, int j)
{ make_rt(i); fa[i] = j; }
void cut(int i, int j)
{ make_rt(i); access(j); splay(j); fa[i] = chl[j][0] = 0; }
void del(int i)
{ make_rt(i); fa[chl[i][0]] = fa[chl[i][1]] = 0, chl[i][0] = chl[i][1] = 0; }
int find_rt(int nd)
{ access(nd), splay(nd); while (chl[nd][0]) nd = chl[nd][0]; return nd; }
bool linked(int i, int j)
{ return find_rt(i) == find_rt(j); }
void insert(int i, int j, int k)
{ rt[++tp] = k, link(i, tp), link(tp, j);}
void split(int i, int j) // 提取区间
{ make_rt(i); access(j); splay(j); }
int n, m;
struct edge {
int x, y, a, b;
void print()
{
printf("%d,%d --> %d,%d\n", x, y, a, b);
}
} egs[maxn];
int read()
{
int a = 0, c;
do c = getchar(); while(!isdigit(c));
while (isdigit(c)) {
a = a*10+c-'0';
c = getchar();
}
return a;
}
int cmp(const edge &a, const edge &b)
{ return a.a < b.a; }
int main()
{
memset(rt, -127/3, sizeof rt);
scanf("%d%d", &n, &m);
tp = n;
for (int i = 1; i <= m; i++)
egs[i].x = read(), egs[i].y = read(), egs[i].a = read(), egs[i].b = read();
sort(egs+1, egs+m+1, cmp);
int ans = INT_MAX, now_a = 0;
for (int i = 1; i <= m; i++) {
if (!linked(egs[i].x, egs[i].y)) insert(egs[i].x, egs[i].y, egs[i].b), now_a = egs[i].a;
else {
split(egs[i].x, egs[i].y);
int mxnd = mx[egs[i].y]; // 最大节点
if (egs[i].b < rt[mxnd]) {
del(mxnd);
insert(egs[i].x, egs[i].y, egs[i].b);
now_a = egs[i].a;
}
}
if (linked(1, n)) {
split(1, n);
ans = min(ans, now_a + rt[mx[n]]);
}
}
if (ans == INT_MAX) puts("-1");
else cout << ans << endl;
return 0;
}