LCT学习笔记

[HNOI2010]弹飞绵羊

可以省去换根之类的操作

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)
#define pushup(x) (sze[x] = sze[tr[x][0]] + sze[tr[x][1]] + 1)

using namespace std;


inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

inline void write(int x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 2e5 + 10;

int n, m, fa[N], tr[N][2], sze[N];

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    pushup(y), pushup(x);
}

void splay(int x) {
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, pushup(x);
}

int main() {
    n = read();
    for (int i = 1; i <= n; i++) {
        int k = read(); sze[i] = 1;
        if (i + k <= n) fa[i] = i + k;
    }
    for (m = read(); m; m--) {
        int opt = read(), st = read() + 1;
        if (opt == 1) access(st), splay(st), write(sze[st], '
');
        else {
            int k = read();
            access(st), splay(st), tr[st][0] = fa[tr[st][0]] = 0; // cut
            if (st + k <= n) fa[st] = st + k; // link
        }
    }
    return 0;
} 

图上询问

题意:给定$n$个点$m$条边的无向图,$q$次询问,每次询问编号在$[l_i,r_i]$的边构成多少连通块,$n,m,q leq 2 imes 10^5$

解法:

  对于$r in [1,n]$,求$[1,r]$的最大生成森林(以边的编号为边权)

  连通块数=点数-最大生成森林的边数

  主席树维护每个前缀$[1,r]$中,每种编号的边是否出现过,即可做到在线解决

  $mathcal O((n + m) log (n+m)+q log m)$

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)

using namespace std;


inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

inline void write(int x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 2e5 + 10, M = 2e5 + 10;

int n, m, q, f[N + M], fa[N + M], tr[N + M][2], val[N + M]; bool rev[N + M];

int find(int u) {
    return f[u] == u ? u : f[u] = find(f[u]);
}

void reverse(int x) {
    swap(tr[x][0], tr[x][1]), rev[x] ^= 1;
}

void pushdown(int x) {
    if (rev[x]) {
        if (tr[x][0]) reverse(tr[x][0]);
        if (tr[x][1]) reverse(tr[x][1]);
        rev[x] = 0;
    }
}

void pushall(int x) {
    if (!isroot(x)) pushall(fa[x]);
    pushdown(x);
}

void updata(int x) {
    val[x] = (x <= n ? 0x3f3f3f3f : x);
    if (tr[x][0] && val[x] > val[tr[x][0]]) val[x] = val[tr[x][0]];
    if (tr[x][1] && val[x] > val[tr[x][1]]) val[x] = val[tr[x][1]]; 
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    updata(y), updata(x);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, updata(x);
}

int findrt(int u) {
    access(u), splay(u);
    while (tr[u][0]) pushdown(u), u = tr[u][0];
    return splay(u), u;
}

void makert(int u) {
    access(u), splay(u), reverse(u);
}

void link(int u, int v) {
    makert(u), splay(v), fa[u] = v;
}

void split(int u, int v) {
    makert(u), access(v), splay(v);
}

int rt[M], ls[M * 40], rs[M * 40], sum[M * 40], nodeid;

int modify(int pre, int l, int r, int pos, int delta) {
    int now = ++nodeid;
    sum[now] = sum[pre], ls[now] = ls[pre], rs[now] = rs[pre];
    if (l == r) sum[now] += delta;
    else {
        int mid = (l + r) >> 1;
        if (pos <= mid) ls[now] = modify(ls[pre], l, mid, pos, delta);
        else rs[now] = modify(rs[pre], mid + 1, r, pos, delta);
        sum[now] = sum[ls[now]] + sum[rs[now]];
    }
    return now;
}

int query(int u, int v, int l, int r, int Left, int Right) {
    if (Left <= l && r <= Right) return sum[u] - sum[v];
    int mid = (l + r) >> 1, res = 0;
    if (Left <= mid) res += query(ls[u], ls[v], l, mid, Left, Right);
    if (mid < Right) res += query(rs[u], rs[v], mid + 1, r, Left, Right);
    return res;
}

int main() {
    freopen("graph.in", "r", stdin);
    freopen("graph.out", "w", stdout);
    n = read(), m = read(), q = read();
    for (int i = 1; i <= n; i++) f[i] = i;
    for (int i = 1; i <= m; i++) {
        rt[i] = rt[i - 1];
        int u = read(), v = read();
        if (u == v) continue;
        if (find(u) != find(v)) f[find(u)] = find(v), link(u, i + n), link(i + n, v);
        else {
            split(u, v);
            int now = val[v];
            splay(now), fa[tr[now][0]] = fa[tr[now][1]] = 0;
            link(u, i + n), link(i + n, v);
            rt[i] = modify(rt[i], 1, m, now - n, -1);    
        }
        rt[i] = modify(rt[i], 1, m, i, 1);
    }
    for (int i = 1; i <= q; i++) {
        int l = read(), r = read();
        int tot = query(rt[r], rt[l - 1], 1, m, l, m);
        write(n - tot, '
');
    }
    return 0;
}

「NOI2014」魔法森林

#include <bits/stdc++.h>
 
#define check(x) (tr[fa[x]][1] == x) 
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)
 
using namespace std;
 
inline int read() {
    int out = 0;
    bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}
 
 
const int N = 5e4 + 5, M = 1e5 + 5;
 
int n, m, ans, val[N + M], tr[N + M][2], fa[N + M], Max[N + M], id[N + M], f[N];
 
bool rev[N + M];
 
struct edge {
    int u, v, a, b;
    void readdata() {
        u = read(), v = read(), a = read(), b = read();
    }
    bool operator < (const edge &g) const {
        return a < g.a;
    }
} e[M];
 
int find(int u) {
    return f[u] == u ? u : f[u] = find(f[u]);
}
 
void updata(int u) {
    Max[u] = val[u], id[u] = u;
    if (tr[u][0] && Max[u] < Max[tr[u][0]]) Max[u] = Max[tr[u][0]], id[u] = id[tr[u][0]];
    if (tr[u][1] && Max[u] < Max[tr[u][1]]) Max[u] = Max[tr[u][1]], id[u] = id[tr[u][1]];
}
 
void reverse(int u) {
    swap(tr[u][0], tr[u][1]), rev[u] ^= 1;
}
 
void pushdown(int u) {
    if (rev[u]) {
        if (tr[u][0]) reverse(tr[u][0]);
        if (tr[u][1]) reverse(tr[u][1]);
        rev[u] = 0;
    }
}
 
void pushall(int u) {
    if (!isroot(u)) pushall(fa[u]);
    pushdown(u);
}
 
void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    updata(y), updata(x);
}
 
void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x], z = fa[y];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}
 
void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, updata(x);
}
 
int findrt(int u) {
    access(u), splay(u);
    while (tr[u][0]) pushdown(u), u = tr[u][0];
    return splay(u), u;
}
 
void makert(int u) {
    access(u), splay(u), reverse(u);
}
 
void link(int u, int v) {
    makert(v), splay(u), fa[v] = u;
}
 
void cut(int u, int v) {
    makert(v), splay(v), fa[u] = tr[v][1] = 0, updata(v);
}
 
void split(int u, int v) {
    makert(u), access(v), splay(v);
}
 
 
int main() {
    n = read(), m = read(), ans = 0x3f3f3f3f;
    for (int i = 1; i <= m; i++) e[i].readdata();
    sort(e + 1, e + m + 1);
    for (int i = 1; i <= n; i++) f[i] = i;
    for (int i = 1; i <= m; i++) {
        val[i + n] = e[i].b; int u = e[i].u, v = e[i].v;
        if (u == v) continue;
        if (find(u) != find(v)) link(u, i + n), link(i + n, v), f[find(u)] = find(v);
        else {
            split(u, v);
            int now = id[v], b = Max[v];
            if (b <= e[i].b) continue;
            splay(now), fa[tr[now][0]] = fa[tr[now][1]] = 0;
            link(u, i + n), link(i + n, v);
        }
        if (find(1) == find(n)) split(1, n), ans = min(ans, Max[n] + e[i].a);
    }
    cout << (ans == 0x3f3f3f3f ? -1 : ans) << endl;
    return 0;
}

最小差值生成树

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)

using namespace std;


inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

inline void write(int x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}


const int N = 5e4 + 10, M = 2e5 + 10;

struct edge {
    int u, v, w;
    void readdata() {
        u = read(), v = read(), w = read();
    }
    bool operator < (const edge &g) const {
        return w < g.w;
    }
} e[M];

int n, m, ans = 0x3f3f3f3f, cnt, f[N], fa[N + M], tr[N + M][2], val[N + M], Min[N + M], id[N + M];

bool vis[M], rev[N + M]; int l;

int find(int u) {
    return f[u] == u ? u : f[u] = find(f[u]);
}

void updata(int u) {
    Min[u] = val[u], id[u] = u;
    if (tr[u][0] && Min[u] > Min[tr[u][0]]) Min[u] = Min[tr[u][0]], id[u] = id[tr[u][0]];
    if (tr[u][1] && Min[u] > Min[tr[u][1]]) Min[u] = Min[tr[u][1]], id[u] = id[tr[u][1]];
}

void reverse(int u) {
    swap(tr[u][0], tr[u][1]), rev[u] ^= 1;
}

void pushdown(int u) {
    if (rev[u]) {
        if (tr[u][0]) reverse(tr[u][0]);
        if (tr[u][1]) reverse(tr[u][1]);
        rev[u] = 0;
    }
}

void pushall(int u) {
    if (!isroot(u)) pushall(fa[u]);
    pushdown(u);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    updata(y), updata(x);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, updata(x);
} 

void makert(int u) {
    access(u), splay(u), reverse(u);
}

int findrt(int u) {
    access(u), splay(u);
    while (tr[u][0]) pushdown(u), u = tr[u][0];
    return u;
}

void link(int u, int v) {
    makert(u), splay(u), fa[u] = v;
}

void cut(int u, int v) {
    makert(u), splay(u), fa[v] = tr[u][1] = 0, updata(u); 
}

void split(int u, int v) {
    makert(u), access(v), splay(v);
}

int main() {
    n = read(), m = read();
    for (int i = 1; i <= m; i++) e[i].readdata();
    sort(e + 1, e + m + 1);
    for (int i = 1; i <= n; i++) f[i] = i, val[i] = 0x3f3f3f3f;
    cnt = n;
    for (int i = 1; i <= m; i++) {
        int u = e[i].u, v = e[i].v; val[i + n] = e[i].w;
        if (u == v) continue;
        if (find(u) != find(v)) f[find(u)] = find(v), cnt--, link(u, i + n), link(i + n, v), vis[i] = true;
        else {
            split(u, v);
            int now = id[v];
            splay(now), fa[tr[now][0]] = fa[tr[now][1]] = 0, vis[now - n] = false;
            link(u, i + n), link(i + n, v), vis[i] = true;
        }
        while (!vis[l]) l++;
        if (cnt == 1) ans = min(ans, e[i].w - e[l].w);
    }
    write(ans, '
');
    return 0;
}

 

魔术师

逆向考虑

优先级最低的一定是最后删的

在这之前是先删优先级次低的到优先级最低的链上的点

再之前是删优先级第三低的到优先级次低和优先级最低的链上的点

我们把优先级最低的作为根

按优先级从高到低,依次将它们到根的路径染上一种比之前都深的颜色(后面的覆盖前面的,染色过程相当于$access$操作)

发现把某点优先级设为最低也相当于$makeroot$操作

查询时则是维护$u$所在的实链中深度大于它的点数加上比它浅的实链的总点数

第一部分$splay$上直接维护,第二部分加个树状数组即可

有个东西我tmd改到自闭,就树状数组的上界是$n+m$,可是对于多组询问修改,我习惯这样写(((((((

这是什么傻逼。。。

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)
#define pushup(x) (sze[x] = sze[tr[x][0]] + sze[tr[x][1]] + 1)

using namespace std;


inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

inline void write(int x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 5e5 + 10;

int n, m, fa[N], tr[N][2], sze[N], id[N], tot, sum[N << 1]; bool rev[N];

int head[N], to[N << 1], nxt[N << 1], cnt;

void add_edge(int u, int v) {
    to[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}

void dfs(int u, int las) {
    fa[u] = las;
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i]; 
        if (v == las) continue;
        dfs(v, u);
    }
}

void add(int k, int delta) {
    for ( ; k <= n + m; k += (k & -k)) sum[k] += delta;
}

int ask(int k) {
    int res = 0;
    for ( ; k; k ^= (k & -k)) res += sum[k];
    return res;
}

void reverse(int x) {
    swap(tr[x][0], tr[x][1]), rev[x] ^= 1;
}

void pushdown(int x) {
    if (rev[x]) {
        if (tr[x][0]) reverse(tr[x][0]);
        if (tr[x][1]) reverse(tr[x][1]);
        rev[x] = false;
    }
    if (tr[x][0]) id[tr[x][0]] = id[x];
    if (tr[x][1]) id[tr[x][1]] = id[x];
}

void pushall(int x) {
    if (!isroot(x)) pushall(fa[x]);
    pushdown(x);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    pushup(y), pushup(x);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
    pushup(x);
}

void access(int x) {
    int d = 0, u = x, y = 0;
    for (y = 0; x; y = x, x = fa[x]) {
        splay(x);
        int s = 1;
        if (tr[x][0]) s += sze[tr[x][0]];
        if (id[x]) add(id[x], -s); pushdown(x);
        d += s, tr[x][1] = y, pushup(x);
    }
    splay(y), add(id[y] = ++tot, d); // 先splay才能保证整条链改 
}

void makert(int x) {
    access(x), splay(x), reverse(x);
}

int main() {
    n = read(), m = read();
    for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        add_edge(u, v), add_edge(v, u);
    }
    dfs(n, 0);
    for (int i = 1; i <= n; i++) access(i);
    for (int i = 1; i <= m; i++) {
        char opt = getchar();
        while (opt != 'M' && opt != 'Q') opt = getchar();
        if (opt == 'M') makert(read());
        else {
            int u = read(); splay(u);
            write(ask(id[u]) - sze[tr[u][0]], '
');
        }
    }
    return 0;
} 

[AHOI2005] 航线规划

删边不好搞,把操作倒序进行,则删边改为加边

每次加边就是把$LCT$维护的那棵树上对应两点之间的路径给$split$出来,打个全赋为$0$的标记

就不写了吧,思路错了别找我哇2333

 

「BJOI2014」大融合

类似$DDP$,将虚儿子$size$和记做$s[u]$,总的$size$和记做$sze[u]$,然后转移

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)
#define pushup(x) (sze[x] = sze[tr[x][0]] + sze[tr[x][1]] + s[x] + 1)

using namespace std;


inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

inline void write(long long x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 1e5 + 10;

int n, m, fa[N], tr[N][2], s[N], sze[N]; bool rev[N];

void reverse(int x) {
    swap(tr[x][0], tr[x][1]), rev[x] ^= 1;
}

void pushdown(int x) {
    if (rev[x]) {
        if (tr[x][0]) reverse(tr[x][0]);
        if (tr[x][1]) reverse(tr[x][1]);
        rev[x] = false;
    }
}

void pushall(int x) {
    if (!isroot(x)) pushall(fa[x]);
    pushdown(x);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    pushup(y), pushup(x);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), s[x] += sze[tr[x][1]], s[x] -= sze[tr[x][1] = y]/*, pushup(x)*/;
}

void makert(int x) {
    access(x), splay(x), reverse(x);
}

void split(int u, int v) {
    makert(u), access(v), splay(v);
}

void link(int u, int v) {
    makert(u), access(v), splay(v), s[fa[u] = v] += sze[u], pushup(v);
} // 注意这里要access,才无需再向上更新 

int main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) sze[i] = 1;
    for (int i = 1; i <= m; i++) {
        char opt = getchar();
        while (opt != 'A' && opt != 'Q') opt = getchar();
        int u = read(), v = read();
        if (opt == 'A') link(u, v);
        else {
            split(u, v);
            write((s[u] + 1ll) * (s[v] + 1ll), '
');
        }
    }
    return 0;
}

 

「SHOI2014」三叉神经树

每次修改的只有可能是该点往上的一段连续区间

$val[u]$为$u$儿子中为$1$的节点个数

记录$splay$所维护实链上深度最大的$val$非$1$节点和$val$非$2$节点

对链上进行区间改和单点改

区间改所改的子树只有可能全为$1$或全为$2$,$pushdown$时只需要$swap$深度最大的非$1$节点和非$2$节点

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)

using namespace std;

inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
}

const int N = 5e5 + 10;

int n, fa[3 * N], tr[N][2], deg[N], val[3 * N], id1[N], id2[N], tag[N]; queue<int> q;

void pushup(int x) {
    id1[x] = id2[x] = -1;
    int ls = tr[x][0], rs = tr[x][1];
    if (rs) id1[x] = id1[rs], id2[x] = id2[rs];
    if (id1[x] == -1 && val[x] != 1) id1[x] = x;
    if (id2[x] == -1 && val[x] != 2) id2[x] = x;
    if (ls && id1[x] == -1) id1[x] = id1[ls];
    if (ls && id2[x] == -1) id2[x] = id2[ls]; 
}

void change(int u, int delta) {
    if (u) tag[u] += delta, val[u] += delta, swap(id1[u], id2[u]);
}

void pushdown(int u) {
    if (tag[u]) {
        if (tr[u][0]) change(tr[u][0], tag[u]);
        if (tr[u][1]) change(tr[u][1], tag[u]);
        tag[u] = 0;
    }
}

void pushall(int u) {
    if (!isroot(u)) pushall(fa[u]);
    pushdown(u);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    pushup(y);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
    pushup(x);
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, pushup(x);
}

int main() {
    n = read();
    for (int i = 1; i <= n; i++) {
        int a = read(), b = read(), c = read();
        fa[a] = fa[b] = fa[c] = i, deg[i] = 3;
    }
    for (int i = n + 1; i <= 3 * n + 1; i++) val[i] = read(), q.push(i);
    while (q.size()) {
        int u = q.front(), v = fa[u]; q.pop();
        val[v] += (u > n ? val[u] : (int)(val[u] >= 2)), deg[v]--;
        if (deg[v] == 0) q.push(v);
    }
    int ans = (val[1] >= 2);
    for (int T = read(); T; T--) {
        int u = read(), op = (val[u] ^= 1);
        access(u = fa[u]), splay(u);
        if (op) {
            if (id1[u] == -1) change(u, 1), ans ^= 1;
            else splay(u = id1[u]), change(tr[u][1], 1), val[u]++, pushup(u);
        }
        else {
            if (id2[u] == -1) change(u, -1), ans ^= 1;
            else splay(u = id2[u]), change(tr[u][1], -1), val[u]--, pushup(u);
        }
        puts(ans ? "1" : "0");
    }
    return 0;
    
} 

 

「SDOI2017」树点涂色

涂色操作相当于$LCT$的$access$操作

定义$val[x]$为$x$到根的路径的权值

路径$x ightarrow y$的权值为$val[x]+val[y]-2 imes val[lca(x,y)]+1$

维护$val$则考虑$access$时,$u$点的父亲边由虚变实$u$子树$-1$,由实变虚子树$+1$

注意$LCT$上的父子关系不代表原树上的父子关系

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)

#define lson u << 1, l, mid
#define rson u << 1 | 1, mid + 1, r

using namespace std;

inline int read() {
    int out = 0; bool flag = false;
    register char cc = getchar();
    while (cc < '0' || cc > '9') {
        if (cc == '-') flag = true;
        cc = getchar();
    }
    while (cc >= '0' && cc <= '9') {
        out = (out << 3) + (out << 1) + (cc ^ 48);
        cc = getchar();
    }
    return flag ? -out : out;
} 

inline void write(int x, char ch) {
    if (x < 0) putchar('-'), x = -x;
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 1e5 + 10;

int n, m, fa[N], tr[N][2], dfn[N], id[N], times, f[N][17], sze[N], dep[N];

int head[N], to[N << 1], nxt[N << 1], cnt;

void add_edge(int u, int v) {
    to[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}

void dfs(int u, int las) {
    f[u][0] = fa[u] = las, dfn[u] = ++times, id[times] = u, sze[u] = 1, dep[u] = dep[las] + 1;
    for (int i = 1; i <= 16; i++) f[u][i] = f[f[u][i - 1]][i - 1];
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if (v == las) continue;
        dfs(v, u), sze[u] += sze[v];
    }
}

int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = 16; i >= 0; i--) if (dep[f[x][i]] >= dep[y]) x = f[x][i];
    if (x == y) return x;
    for (int i = 16; i >= 0; i--) if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}

int Max[N << 2], inc[N << 2];

void build(int u, int l, int r) {
    if (l == r) Max[u] = dep[id[l]]; // dep的下标是点的编号 
    else {
        int mid = (l + r) >> 1;
        build(lson), build(rson);
        Max[u] = max(Max[u << 1], Max[u << 1 | 1]);
    }
}

void pushdown(int u) {
    Max[u << 1] += inc[u], Max[u << 1 | 1] += inc[u];
    inc[u << 1] += inc[u], inc[u << 1 | 1] += inc[u];
    inc[u] = 0;
}

void modify(int u, int l, int r, int Left, int Right, int delta) {
    if (Left <= l && r <= Right) Max[u] += delta, inc[u] += delta;
    else {
        pushdown(u);
        int mid = (l + r) >> 1;
        if (Left <= mid) modify(lson, Left, Right, delta);
        if (mid < Right) modify(rson, Left, Right, delta);
        Max[u] = max(Max[u << 1], Max[u << 1 | 1]);
    } 
}

int query(int u, int l, int r, int Left, int Right) {
    if (Left <= l && r <= Right) return Max[u];
    pushdown(u);
    int mid = (l + r) >> 1, ans = 0;
    if (Left <= mid) ans = max(ans, query(lson, Left, Right));
    if (mid < Right) ans = max(ans, query(rson, Left, Right));
    return ans;
}

int query(int u, int l, int r, int pos) {
    if (l == r) return Max[u];
    pushdown(u);
    int mid = (l + r) >> 1;
    if (pos <= mid) return query(lson, pos);
    else return query(rson, pos);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
}

void splay(int x) {
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

int findrt(int x) {
    while (tr[x][0]) x = tr[x][0];
    return x;
}

void add(int u, int delta) {
    if (u) {
        u = findrt(u); // 注意LCT上的父子关系不代表原树上的父子关系 
        modify(1, 1, n, dfn[u], dfn[u] + sze[u] - 1, delta);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), add(tr[x][1], 1), add(tr[x][1] = y, -1);
}

int ask(int u) {
    return query(1, 1, n, dfn[u], dfn[u] + sze[u] - 1);
}

int main() {
    n = read(), m = read();
    for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        add_edge(u, v), add_edge(v, u);
    }
    dfs(1, 0), build(1, 1, n);
    for (int i = 1; i <= m; i++) {
        int op = read();
        if (op == 1) access(read());
        else if (op == 2) {
            int x = read(), y = read(), Lca = lca(x, y);
            x = query(1, 1, n, dfn[x]), y = query(1, 1, n, dfn[y]), Lca = query(1, 1, n, dfn[Lca]);
            // 注意单点查也要考虑到dfn[x]才是线段树上的位置 
            write(x + y - 2 * Lca + 1, '
');
        }
        else write(ask(read()), '
');
    }
    return 0;
}

 

[Ynoi2017] 由乃的 OJ

调了一年终于过了,各种傻逼错误,我好菜哇

可以并行计算$k$位

设$f_0$为一开始全为$0$,最后会变成什么

  $f_1$为一开始全为$1$,最后会变成什么

因为这k位间互不影响,所以每组询问最后再$Theta(k)$计算答案

但注意维护时需要翻转链

所以需要同时维护每条实链上从浅到深的信息和从深到浅的信息

$reverse$时记得要交换

$Theta(n+q (k+log n))$

#include <bits/stdc++.h>

#define check(x) (tr[fa[x]][1] == x)
#define isroot(x) (tr[fa[x]][0] != x && tr[fa[x]][1] != x)

using namespace std;

typedef unsigned long long ull;

ull read() {
    ull out = 0;
    register char cc = getchar();
    while (cc < '0' || cc > '9') cc = getchar();
    while (cc >= '0' && cc <= '9') out = (out << 3) + (out << 1) + (cc ^ 48), cc = getchar();
    return out;
}

void write(ull x, char ch) {
    if (x == 0) putchar('0');
    else {
        int num = 0; char cc[22];
        while (x) cc[++num] = x % 10 + 48, x /= 10;
        while (num) putchar(cc[num--]);
    }
    putchar(ch);
}

const int N = 1e5 + 10;

int head[N], to[N << 1], nxt[N << 1], cnt;

void add_edge(int u, int v) {
    to[++cnt] = v, nxt[cnt] = head[u], head[u] = cnt;
}

int n, m, k, opt[N], fa[N], tr[N][2]; 

ull a, b, l0[N], l1[N], r0[N], r1[N], val[N]; bool rev[N];

void pushup(int x) {
    if (opt[x] == 1) l0[x] = r0[x] = 0, l1[x] = r1[x] = val[x];
    else if (opt[x] == 2) l0[x] = r0[x] = val[x], l1[x] = r1[x] = (~0ull);
    else if (opt[x] == 3) l0[x] = r0[x] = val[x], l1[x] = r1[x] = (~val[x]);
    int ls = tr[x][0], rs = tr[x][1];
    if (ls) {
        ull p = l0[x], q = l1[x]; // 因为分两步合并,所以合并时要先把原来值储存下来 
        l0[x] = (l0[ls] & q) | ((~l0[ls]) & p);
        l1[x] = (l1[ls] & q) | ((~l1[ls]) & p);
        p = r0[x], q = r1[x];
        r0[x] = (p & r1[ls]) | ((~p) & r0[ls]);
        r1[x] = (q & r1[ls]) | ((~q) & r0[ls]);
    }
    if (rs) {
        ull p = l0[x], q = l1[x];
        l0[x] = (p & l1[rs]) | ((~p) & l0[rs]);
        l1[x] = (q & l1[rs]) | ((~q) & l0[rs]);
        p = r0[x], q = r1[x];
        r0[x] = (r0[rs] & q) | ((~r0[rs]) & p);
        r1[x] = (r1[rs] & q) | ((~r1[rs]) & p);
    }
    
}

void reverse(int x) {
    swap(tr[x][0], tr[x][1]), swap(l0[x], r0[x]), swap(l1[x], r1[x]), rev[x] ^= 1;
}

void pushdown(int x) {
    if (rev[x]) {
        if (tr[x][0]) reverse(tr[x][0]);
        if (tr[x][1]) reverse(tr[x][1]);
        rev[x] = false;
    }
}

void pushall(int x) {
    if (!isroot(x)) pushall(fa[x]);
    pushdown(x);
}

void rotate(int x) {
    int y = fa[x], z = fa[y], opt1 = check(x), opt2 = check(y);
    if (!isroot(y)) tr[z][opt2] = x; fa[x] = z;
    tr[y][opt1] = tr[x][opt1 ^ 1], fa[tr[x][opt1 ^ 1]] = y;
    tr[x][opt1 ^ 1] = y, fa[y] = x;
    pushup(y), pushup(x);
}

void splay(int x) {
    pushall(x);
    while (!isroot(x)) {
        int y = fa[x];
        if (!isroot(y)) rotate(check(x) == check(y) ? y : x);
        rotate(x);
    }
}

void access(int x) {
    for (int y = 0; x; y = x, x = fa[x]) splay(x), tr[x][1] = y, pushup(x);
}

void makert(int x) {
    access(x), splay(x), reverse(x);
}

void split(int x, int y) {
    makert(x), access(y), splay(y);
}

void dfs(int u, int las) {
    fa[u] = las;
    for (int i = head[u]; i; i = nxt[i]) {
        int v = to[i];
        if (v == las) continue;
        dfs(v, u);
    }
    pushup(u); 
}

int main() {
    n = read(), m = read(), k = read();
    for (int i = 0; i < k; i++) b |= (1ull << i);
    for (int i = 1; i <= n; i++) opt[i] = read(), val[i] = read();
    for (int i = 1; i < n; i++) {
        int u = read(), v = read();
        add_edge(u, v), add_edge(v, u);
    }
    dfs(1, 0);
    for ( ; m; m--) {
        int op = read(), x = read(), y = read(); ull z = read(); //注意z类型为ull 
        if (op == 1) {
            split(x, y);
            ull ans = 0;
            for (int i = k - 1; i >= 0; i--) {
                if (l0[y] & (1ull << i)) ans |= (1ull << i);
                else if ((1ull << i) <= z && (l1[y] & (1ull << i))) ans |= (1ull << i), z -= (1ull << i);
            }
            write(max(ans, l0[y]), '
');
        }
        else opt[x] = y, val[x] = z, splay(x);
    }
    return 0;
}

 

「ZJOI2016」大森林

「ZJOI2018」历史

 

原文地址:https://www.cnblogs.com/Urushibara-Ruka/p/14472246.html