【BZOJ 1180】OTOCI【LCT】&【树链剖分+并查集】

本文深入探讨了一种涉及图论的复杂算法问题,包括利用LCT(Link-Cut Tree)和树链剖分加并查集两种方法进行在线处理。通过这两种方法解决了节点间的连通性查询、权值更新及路径权值和的计算等问题。

Description

给出n个结点以及每个点初始时对应的权值wi。起始时点与点之间没有连边。有3类操作:
1、bridge A B:询问结点A与结点B是否连通。如果是则输出“no”。否则输出“yes”,并且在结点A和结点B之间连一条无向边。
2、penguins A X:将结点A对应的权值wA修改为X。
3、excursion A B:如果结点A和结点B不连通,则输出“impossible”。否则输出结点A到结点B的路径上的点对应的权值的和。
给出q个操作,要求在线处理所有操作。数据范围:1<=n<=30000, 1<=q<=300000, 0<=wi<=1000。

Input

第一行包含一个整数n(1<=n<=30000),表示节点的数目。第二行包含n个整数,第i个整数表示第i个节点初始时对应的权值。第三行包含一个整数q(1<=n<=300000),表示操作的数目。以下q行,每行包含一个操作,操作的类别见题目描述。任意时刻每个节点对应的权值都是1到1000的整数。

Output

输出所有bridge操作和excursion操作对应的输出,每个一行。

题解

一、LCT

本题最基本的方法

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define N 60010
#define lson node[o].ls
#define rson node[o].rs

struct LCT{
    struct Node{
        int idx,fa,ls,rs,path_fa;
    } node[N];
    int cnt,pos[N],a[N],tot[N];
    bool ch[N];

    inline void maintain(int o)
    {
        if(ch[o]) {
            ch[o] = false;
            ch[lson] = !ch[lson]; ch[rson] = !ch[rson];
            swap(lson,rson);
        }
        tot[o] = tot[lson] + tot[rson] + a[node[o].idx];
    }

    void l_rotate(int o)
    {
        int y = node[o].fa;
        node[o].path_fa = node[y].path_fa;
        node[y].path_fa = 0;
        node[y].rs = lson;
        int tmp = tot[y];
        tot[y] = tot[y] - tot[o] + tot[lson];
        tot[o] = tmp;
        if(lson) node[lson].fa = y;
        node[o].fa = node[y].fa;
        if(node[y].fa) {
            if(y == node[node[y].fa].ls) node[node[y].fa].ls = o;
            else node[node[y].fa].rs = o;
        }
        node[y].fa = o;
        node[o].ls = y;
    }

    void r_rotate(int o)
    {
        int y = node[o].fa;
        node[o].path_fa = node[y].path_fa;
        node[y].path_fa = 0;
        node[y].ls = rson;
        int tmp = tot[y];
        tot[y] = tot[y] - tot[o] + tot[rson];
        tot[o] = tmp;
        if(rson) node[rson].fa = y;
        node[o].fa = node[y].fa;
        if(node[y].fa) {
            if(y == node[node[y].fa].ls) node[node[y].fa].ls = o;
            else node[node[y].fa].rs = o;
        }
        node[y].fa = o;
        node[o].rs = y;
    }

    void splay(int o)
    {
        while(node[o].fa) {
            int pa = node[o].fa;
            maintain(pa);
            if(node[pa].ls) maintain(node[pa].ls);
            if(node[pa].rs) maintain(node[pa].rs);
            if(o == node[node[o].fa].ls) r_rotate(o); else l_rotate(o);
        }
    }

    void access(int o)
    {
        splay(o);
        maintain(o);
        int q = rson;
        rson = node[q].fa = 0;
        node[q].path_fa = o;
        maintain(o);
        for(q = node[o].path_fa;q;q = node[o].path_fa) {
            splay(q);
            maintain(q);
            int r = node[q].rs;
            node[r].fa = 0;
            node[r].path_fa = q;
            node[q].rs = o;
            node[o].fa = q;
            node[o].path_fa = 0;
            maintain(q);
            o = q;
        }
        splay(o);
    }

    int find_root(int o)
    {
        access(o); splay(o);
        maintain(o);
        while(lson) o = lson;
        splay(o);
        return o;
    }

    void evert(int o)
    {
        access(o); splay(o);
        ch[o] = !ch[o];
        maintain(o);
    }

    void cut(int p,int q)
    {
        evert(p);
        access(q); splay(q);
        maintain(q);
        node[node[q].ls].fa = 0;
        node[q].ls = 0;
        maintain(q);
    }

    void link(int p,int q)
    {
        evert(p); splay(p); maintain(p);
        access(q); splay(q); maintain(q);
        node[p].ls = q;
        node[q].fa = p;
        maintain(p);
    }

    int sum(int p,int q)
    {
        evert(p); splay(p); maintain(p);
        access(q); splay(q); maintain(q);
        return tot[q];
    }

    void change(int o,int d,int pre)
    {
        splay(o);
        tot[o] += d - pre;
    }

    void init()
    {
        cnt = 0;
        memset(pos,0,sizeof(pos));
        tot[0] = 0;
    }

    void make_tree(int idx,int d)
    {
        a[idx] = d;
        int o = ++cnt;
        pos[idx] = o;
        node[o].fa = lson = rson = node[o].path_fa = 0;
        tot[o] = d;
        node[o].idx = idx;
    }

    int getroot(int idx) { return node[find_root(idx)].idx; }
    void add_edge(int x,int y) { link(pos[x],pos[y]); }
    void destory(int x,int y) { cut(pos[x],pos[y]); }
    int getsum(int x,int y) { return sum(pos[x],pos[y]); }
    void Change(int x,int d) { change(pos[x],d,a[x]); a[x] = d; }
}T;

int n,u,v,q;
char str[20];

int main()
{
    scanf("%d",&n);
    T.init();
    for(int i = 1;i <= n;i++) {
        scanf("%d",&v);
        T.make_tree(i,v);
    }
    scanf("%d",&q);
    while(q--) {
        scanf("%s%d%d",str,&u,&v);
        if(str[0] == 'b') {
            if(T.getroot(u) == T.getroot(v)) puts("no");
            else {puts("yes"); T.add_edge(u,v);}
        }
        else if(str[0] == 'p') T.Change(u,v);
        else {
            if(T.getroot(u) != T.getroot(v)) puts("impossible");
            else printf("%d\n",T.getsum(u,v));
        }
    }
    return 0;
}

二、树链剖分+并查集

  我个人更喜欢这一种,好写一点。
  考虑到本题没有删边操作,所以一开始将所有的添加边搞出来,确定树的形态(注意,可能是一棵森林,在树链剖分dfs的时候要注意
  然后再用并查集维护连通性即可。

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
#define N 30100
#define M 300100

struct node{int to,next;}e[N<<2];
int head[N],tot;

int tid[N],son[N],fa[N],top[N],dep[N],size[N],Rank[N];
int n,a[N],tim;
bool vis[N];

void init()
{
    tot = tim = 0;
    memset(head,0,sizeof(head));
    memset(son,-1,sizeof(son));
    memset(vis,false,sizeof(vis));
}

void add_edge(int from,int to)
{
    e[++tot].next = head[from];
    head[from] = tot;
    e[tot].to = to;
}

void dfs1(int v,int pa,int deep)
{
    dep[v] = deep; fa[v] = pa; size[v] = 1;
    vis[v] = 1;
    for(int i = head[v];i;i = e[i].next)
        if(e[i].to != pa) {
            dfs1(e[i].to,v,deep+1);
            size[v] += size[e[i].to];
            if(son[v] == -1 || size[e[i].to] > size[son[v]])
                son[v] = e[i].to;
        }
}

void dfs2(int v,int tp)
{
    top[v] = tp; tid[v] = ++tim;
    Rank[tid[v]] = v;
    if(son[v] == -1) return;
    dfs2(son[v],tp);
    for(int i = head[v];i;i=e[i].next)
        if(e[i].to != son[v] && e[i].to != fa[v])
            dfs2(e[i].to,e[i].to);
}

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

#define lson o << 1
#define rson o << 1 | 1
int sum[N<<2];

void build(int o,int l,int r)
{
    if(l == r) {sum[o] = a[Rank[l]]; return;}
    int mid = (l+r)>>1;
    build(lson,l,mid); build(rson,mid+1,r);
    sum[o] = sum[lson] + sum[rson];
}

void update(int o,int l,int r,int pos,int v)
{
    if(l == r) {sum[o] = v; return;}
    int mid = (l+r)>>1;
    if(pos <= mid) update(lson,l,mid,pos,v); else update(rson,mid+1,r,pos,v);
    sum[o] = sum[lson] + sum[rson];
}

int query(int o,int l,int r,int ll,int rr)
{
    if(ll <= l && rr >= r) return sum[o];
    int mid = (l+r)>>1;
    int ans = 0;
    if(ll <= mid) ans += query(lson,l,mid,ll,rr);
    if(rr > mid) ans += query(rson,mid+1,r,ll,rr);
    return ans;
}

int Query(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y]) {
        if(dep[top[x]] < dep[top[y]]) swap(x,y);
        ans += query(1,1,n,tid[top[x]],tid[x]);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y]) swap(x,y);
    ans += query(1,1,n,tid[x],tid[y]);
    return ans;
}

int A[M],B[M],opt[M];
int m;
char str[20];
int main()
{
    init();
    scanf("%d",&n);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    scanf("%d",&m);

    for(int i = 1;i <= n;i++) f[i] = i;
    for(int i = 1;i <= m;i++) {
        scanf("%s%d%d",str,&A[i],&B[i]);
        if(str[0] == 'b') {
            opt[i] = 1;
            int x = find(A[i]),y = find(B[i]);
            if(x != y) {
                f[x] = y;
                add_edge(A[i],B[i]); add_edge(B[i],A[i]);
            }
        }
        if(str[0] == 'p') opt[i] = 2;
        if(str[0] == 'e') opt[i] = 3;
    }
    for(int i = 1;i <= n;i++)
        if(!vis[i]) dfs1(i,0,0),dfs2(i,i);
    build(1,1,n);

    for(int i = 1;i <= n;i++) f[i] = i;
    for(int i = 1;i <= m;i++) {
        if(opt[i] == 1) {
            int x = find(A[i]),y = find(B[i]);
            if(x != y) {puts("yes"); f[x] = y;} else puts("no");
        }
        if(opt[i] == 2) update(1,1,n,tid[A[i]],B[i]);
        if(opt[i] == 3) {
            int x = find(A[i]),y = find(B[i]);
            if(x != y) puts("impossible"); else printf("%d\n",Query(A[i],B[i]));
        }
    }
    return 0;
}
### 树链剖分的适用场景与使用方法 树链剖分是一种高效的数据结构,用于处理树上的路径查询和修改问题。它通过将树分解为若干条不相交的链来优化复杂度,使得许多原本需要 \(O(n)\) 时间的操作可以在 \(O(\log n)\) 时间内完成[^1]。 #### 1. 树链剖分的核心思想 树链剖分的核心在于将树分割成若干条链,这些链可以拼接成树上的任意路径。常见的树链剖分方法包括重链剖分和长链剖分。其中,重链剖分是最常用的一种方法,其基本原理是:对于每个节点,选择其所有子节点中包含节点数最多的子节点作为重儿子,连接重儿子的边称为重边,由重边构成的链称为重链[^2]。 #### 2. 树链剖分的适用场景 树链剖分适用于以下场景: - **路径查询**:例如,求解树上两点之间的最大值、最小值或和等问题。 - **路径修改**:例如,对树上某条路径上的所有节点进行加法或乘法操作。 - **子树查询**:例如,求解某个节点的子树中的最大值、最小值或和等问题。 - **动态维护**:当树的结构或节点属性发生变化时,树链剖分结合线段树等数据结构可以高效地维护这些变化。 #### 3. 树链剖分的应用方法 以下是树链剖分的基本应用步骤: ```python # 树链剖分的实现示例(Python) from collections import defaultdict, deque class TreeChainDecomposition: def __init__(self, n): self.n = n self.adj = defaultdict(list) self.parent = [0] * (n + 1) self.depth = [0] * (n + 1) self.size = [0] * (n + 1) self.heavy = [0] * (n + 1) self.top = [0] * (n + 1) self.pos = [0] * (n + 1) self.rpos = [0] * (n + 1) self.cnt = 0 def add_edge(self, u, v): self.adj[u].append(v) self.adj[v].append(u) def dfs1(self, u, p): self.parent[u] = p self.size[u] = 1 max_subtree = -1 for v in self.adj[u]: if v != p: self.depth[v] = self.depth[u] + 1 self.dfs1(v, u) self.size[u] += self.size[v] if self.size[v] &gt; max_subtree: max_subtree = self.size[v] self.heavy[u] = v def dfs2(self, u, t): self.top[u] = t self.pos[u] = self.cnt self.rpos[self.cnt] = u self.cnt += 1 if self.heavy[u] != 0: self.dfs2(self.heavy[u], t) for v in self.adj[u]: if v != self.parent[u] and v != self.heavy[u]: self.dfs2(v, v) # 示例:初始化并构建树 n = 5 tree = TreeChainDecomposition(n) edges = [(1, 2), (1, 3), (2, 4), (2, 5)] for u, v in edges: tree.add_edge(u, v) tree.dfs1(1, 0) tree.dfs2(1, 1) ``` 上述代码实现了树链剖分的基本框架,包括深度优先搜索(DFS)和重链划分。 #### 4. 实际应用案例 以 bzoj3252 为例,题目要求在树状结构中求解路径的最大价值和。这种问题可以通过树链剖分结合线段树或树状数组来解决。具体步骤如下: - 使用树链剖分将树划分为若干条链。 - 对每条链建立线段树或其他支持快速区间查询和修改的数据结构。 - 在查询或修改时,将路径拆分为若干条链,并分别在线段树上进行操作[^3]。 #### 5. 注意事项 - 树链剖分的时间复杂度通常为 \(O(n \log n)\),适合处理大规模数据。 - 在实际应用中,需要根据问题的具体需求选择合适的剖分方式(如重链剖分或长链剖分)。 - 结合其他数据结构(如线段树、树状数组)可以进一步提升效率。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值