BZOJ 2243 染色(树链剖分)

本文介绍了一种结合树链剖分与线段树合并的数据结构算法,用于解决特定类型的区间查询问题。通过将树状结构进行剖分,并利用线段树维护区间信息,实现高效查询和更新操作。

题意:

思路:

树链剖分+线段树合并
很明显的树链剖分,但是总体有点难维护,首先我们还是要利用树链剖分的性质,从根到叶的编号是从小到大的,那么我们就知道了,对于一对点,我们把其中一个点向他们的LCA向上爬的过程,所求线段树求出的区间是连续的,而对于两个区间,如果两区间连接处的端点颜色相同,就要让ans减一即可

错误及反思:

思路其实不难,但是出了bug就很难受,一看题就知道要200行,写起来有点精神污染
左右端点有点小坑。。。要多想想

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

struct edge{
    int to,next;
}e[N*2];
int num[N*4],lk[N*4],rk[N*4],lazy[N*4];//segtree
int tot,tid,q,n;
int top[N],si[N],fa[N],first[N],son[N],depth[N],id[N],val[N],rnk[N];
char ta[10];

void addedge(int x,int y){
    e[tot].to=y;
    e[tot].next=first[x];
    first[x]=tot++;
    e[tot].to=x;
    e[tot].next=first[y];
    first[y]=tot++;
}

void dfs1(int now,int bef,int dep){
    fa[now]=bef;
    depth[now]=dep;
    si[now]=1;
    for(int i=first[now];i!=-1;i=e[i].next)
        if(e[i].to!=bef){
            dfs1(e[i].to,now,dep+1);
            si[now]+=si[e[i].to];
            if(son[now]==-1) son[now]=e[i].to;
            else son[now]=si[e[i].to]>si[son[now]]?e[i].to:son[now];
        }
}

void dfs2(int now,int tp){
    top[now]=tp;
    id[now]=tid++;
    if(son[now]!=-1) dfs2(son[now],tp);
    for(int i=first[now];i!=-1;i=e[i].next)
        if(e[i].to!=fa[now]&&e[i].to!=son[now])
            dfs2(e[i].to,e[i].to);
}

void init(){
    memset(lazy,-1,sizeof(lazy));
    tot=0; tid=1;
    memset(first,-1,sizeof(first));
    memset(son,-1,sizeof(son));
}

void pushup(int rt){
    lk[rt]=lk[rt<<1];
    rk[rt]=rk[rt<<1|1];
    num[rt]=num[rt<<1]+num[rt<<1|1]-(rk[rt<<1]==lk[rt<<1|1]);
}

void pushdown(int rt){
    if(lazy[rt]!=-1){
        num[rt<<1]=1;
        lk[rt<<1]=lazy[rt];
        rk[rt<<1]=lazy[rt];
        lazy[rt<<1]=lazy[rt];

        num[rt<<1|1]=1;
        lk[rt<<1|1]=lazy[rt];
        rk[rt<<1|1]=lazy[rt];
        lazy[rt<<1|1]=lazy[rt];

        lazy[rt]=-1;
    }
}
//初始化
void build(int l,int r,int rt){
    if(l==r){
        num[rt]=1;
        lk[rt]=val[rnk[l]];
        rk[rt]=val[rnk[l]];
        return ;
    }
    int m=(l+r)/2;
    build(lson);
    build(rson);
    pushup(rt);
}
//染色
void change(int v,int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r){
        lazy[rt]=v;
        lk[rt]=v;
        rk[rt]=v;
        num[rt]=1;
        return ;
    }
    pushdown(rt);
    int m=(l+r)/2;
    if(m>=L) change(v,L,R,lson);
    if(m<R) change(v,L,R,rson);
    pushup(rt);
    return ;
}
//找出这段的个数和端点颜色
int query(int &lc,int &rc,int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r){
        if(L==l) lc=lk[rt];
        if(R==r) rc=rk[rt];
        return num[rt];
    }
    pushdown(rt);
    int m=(l+r)/2;
    int ans=0;
    bool z=false,y=false;
    if(m>=L){
        ans+=query(lc,rc,L,R,lson);
        z=true;
    }
    if(m<R){
        ans+=query(lc,rc,L,R,rson);
        y=true;
    }
    pushup(rt);
    if(z&&y)
        ans-=rk[rt<<1]==lk[rt<<1|1];
    return ans;
}

//染色
void modify(int L,int R,int v)
{
    int f1=top[L],f2=top[R];
    while(f1!=f2)
    {
        if(depth[f1]<depth[f2])
        {
            swap(f1,f2);
            swap(L,R);
        }
        change(v,id[f1],id[L],1,n,1);
        L=fa[f1];
        f1=top[L];
    }
    if(depth[L]>depth[R]) swap(L,R);
    change(v,id[L],id[R],1,n,1);
}

//找段数
int solve(int L,int R)
{
    int f1=top[L],f2=top[R];
    int ans=0,lo=-1,ro=-1;
    while(f1!=f2)
    {
        if(depth[f1]<depth[f2]){
            int tb,tc;
            ans+=query(tb,tc,id[f2],id[R],1,n,1);
            if(tc==ro) ans--;
            ro=tb;
            R=fa[f2];
            f2=top[R];
        }
        else{
            int tb,tc;
            ans+=query(tb,tc,id[f1],id[L],1,n,1);
            if(tc==lo) ans--;
            lo=tb;
            L=fa[f1];
            f1=top[L];
        }
    }
    if(depth[L]>depth[R]){
        int tb,tc;
        ans+=query(tb,tc,id[R],id[L],1,n,1);
        if(tc==lo) ans--;
        if(tb==ro) ans--;
    }
    else{
        int tb,tc;
        ans+=query(tb,tc,id[L],id[R],1,n,1);
        if(tb==lo) ans--;
        if(tc==ro) ans--;
    }
    return ans;
}

int main(){
    init();
    scanf("%d%d",&n,&q);
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=0,u,v;i<n-1;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    dfs1(1,1,1);
    dfs2(1,1);
    for(int i=1;i<=n;i++)
        rnk[id[i]]=i;
    build(1,n,1);
    while(q--){
        scanf("%s",ta);
        if(ta[0]=='Q'){
            int tb,tc;
            scanf("%d%d",&tb,&tc);
            printf("%d\n",solve(tb,tc));
        }
        else{
            int tb,tc,td;
            scanf("%d%d%d",&tb,&tc,&td);
            modify(tb,tc,td);
        }
    }
}
### 树链剖分的适用场景与使用方法 树链剖分是一种高效的数据结构,用于处理树上的路径查询和修改问题。它通过将树分解为若干条不相交的链来优化复杂度,使得许多原本需要 \(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] > 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)\),适合处理大规模数据。 - 在实际应用中,需要根据问题的具体需求选择合适的剖分方式(如重链剖分或长链剖分)。 - 结合其他数据结构(如线段树、树状数组)可以进一步提升效率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值