[SDOI2011染色]树链剖分

本文详细介绍了如何使用树链剖分算法来高效地处理树形结构上的区间更新与查询问题。具体包括节点颜色更新、路径上颜色段数的查询等操作,并提供了完整的代码实现。

题意:

将路径上的点全部变成c
询问路径上的颜色段数。

树链剖分:

维护一下颜色段数,左端颜色,右端颜色,注意询问的时候要push_down(),还有合并时要注意判断,而不是简单的直接相加。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#define lc o<<1
#define rc o<<1|1
using namespace std;
const int maxn=100010;
int a[maxn];
vector<int>g[maxn];
int id[maxn],son[maxn],top[maxn],size[maxn],fa[maxn],dep[maxn];
int ql,qr,n,m,cnt;
void add(int x,int y){
    g[x].push_back(y);
    g[y].push_back(x);
}
void dfs1(int x){
    size[x]=1;
    for(int i=0;i<g[x].size();i++){
        int v=g[x][i];
        if(v==fa[x]) continue;
        fa[v]=x;
        dep[v]=dep[x]+1;
        dfs1(v);
        size[x]+=size[v];
        if(size[v]>size[son[x]]) son[x]=v;
    }
}
void dfs2(int x){
    id[x]=++cnt;
    if(!son[x]) return;
    top[son[x]]=top[x];
    dfs2(son[x]);
    for(int i=0;i<g[x].size();i++){
        int v=g[x][i];
        if(id[v]) continue;
        top[v]=v;
        dfs2(v);
    }
}
int sum[maxn<<2],cl[maxn<<2],cr[maxn<<2],all[maxn<<2];
void push_down(int o,int l,int r){
    if(all[o]){
        all[lc]=all[rc]=all[o];
        sum[lc]=sum[rc]=1;
        cl[lc]=cr[lc]=cl[rc]=cr[rc]=all[o];
        all[o]=0;
    }
}
void maintain(int o,int l,int r){
    int mid=(l+r)>>1;
    sum[o]=sum[lc]+sum[rc];
    if(cr[lc]==cl[rc]) sum[o]--;
    cl[o]=cl[lc];cr[o]=cr[rc];
}
void create_tree(int o,int l,int r,int x,int y){
    if(l==r){
        sum[o]=1;cl[o]=cr[o]=all[o]=y;
        return;
    }
    push_down(o,l,r);
    int mid=(l+r)>>1;
    if(x<=mid) create_tree(lc,l,mid,x,y);
    else create_tree(rc,mid+1,r,x,y);
    maintain(o,l,r);
}
void modify(int o,int l,int r,int val){
    if(ql<=l && qr>=r){
        all[o]=val;cl[o]=cr[o]=val;sum[o]=1;
        return;
    }
    int mid=(l+r)>>1;
    push_down(o,l,r);
    if(ql<=mid) modify(lc,l,mid,val);
    if(qr>mid) modify(rc,mid+1,r,val);
    maintain(o,l,r);
}
int find(int o,int l,int r,int pos){
    if(l==r){
        return cl[o];
    }
    push_down(o,l,r);
    int mid=(l+r)>>1;
    if(pos<=mid)  return find(lc,l,mid,pos);
    else return find(rc,mid+1,r,pos);
}
int Query(int o,int l,int r){
    if(ql<=l && qr>=r){
        return sum[o];
    }
    push_down(o,l,r);
    int mid=(l+r)>>1;
    int ans=0;
    if(ql<=mid) ans+=Query(lc,l,mid);
    if(qr>mid) ans+=Query(rc,mid+1,r);
    if(ql<=mid && qr>mid && cl[rc]==cr[lc]) ans--;
    return ans;
}
void change(int a,int b,int c){
    while(top[a]!=top[b]){
        if(dep[top[a]]<dep[top[b]]) swap(a,b);
        ql=id[top[a]],qr=id[a];
        modify(1,1,n,c);
        a=fa[top[a]];
    }
    if(dep[a]<dep[b]) swap(a,b);
    ql=id[b],qr=id[a];
    modify(1,1,n,c);
}
int query(int a,int b){
    int ans=0;
    while(top[a]!=top[b]){
        if(dep[top[a]]<dep[top[b]]) swap(a,b);
        ql=id[top[a]],qr=id[a];
        ans+=Query(1,1,n);
        if(find(1,1,n,id[top[a]])==find(1,1,n,id[fa[top[a]]])){
            ans--;
        }
        a=fa[top[a]];
    }
    if(dep[a]<dep[b]) swap(a,b);
    ql=id[b],qr=id[a];
    ans+=Query(1,1,n);
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<n;i++){
        int x,y;
        scanf("%d%d",&x,&y);add(x,y);
    }
    dep[1]=1;top[1]=1;
    dfs1(1);
    dfs2(1);
    for(int i=1;i<=n;i++){ 
        create_tree(1,1,n,id[i],a[i]);
    }
    while(m--){
        char s[10];
        int a,b,c;
        scanf("%s",s);
        if(s[0]=='C'){
            scanf("%d%d%d",&a,&b,&c);
            change(a,b,c);
        }else{
            scanf("%d%d",&a,&b);
            printf("%d\n",query(a,b));
        }
    }
    return 0;
}

^_^

### 树链剖分中的染色算法实现 树链剖分是一种高效的树形结构优化方法,常用于处理路径和子树上的复杂查询与修改操作。对于涉及染色的操作,通常可以通过树链剖分配合线段树或其他支持区间更新的数据结构来完成。 #### 基本原理 树链剖分的核心思想是将树分解为多条重链,并通过 `dfn` 编号将其映射到一维数组上[^1]。这样可以方便地使用线段树等数据结构维护这些重链的属性。具体来说: - **重儿子**:每个节点的儿子中,子树大小最大的称为该节点的重儿子。 - **重链**:从某个节点出发沿着重儿子一路向下形成的链条称为重链。 - **轻边**:连接当前节点与其非重儿子的边称为轻边。 通过对树进行上述划分后,任意两点间的路径都可以被划分为不超过 \( O(\log n) \) 条重链和轻边[^2]。 #### 染色问题的具体实现 针对题目描述中的染色问题,我们需要设计一种能够高效执行以下两类操作的方法: 1. 修改路径上的所有点的颜色。 2. 查询某路径上的颜色段数。 以下是具体的解决方案及其代码实现。 --- ##### 数据结构的选择 为了快速响应路径上的批量修改以及统计颜色段的数量,可以选择如下组合: - 使用 **线段树** 维护每条重链上的信息。 - 对于颜色段计数问题,可以在每个线段树节点存储额外的状态变量,比如左端点颜色、右端点颜色、总颜色段数目等。 --- ##### 关键状态定义 假设我们已经完成了树链剖分并构建好了对应的线段树,则需要在线段树节点中记录以下几个字段: - `sum`: 当前区间的颜色段总数。 - `left_color`: 当前区间的左端点颜色。 - `right_color`: 当前区间的右端点颜色。 - `lazy_tag`: 表示是否有延迟标记(即整个区间是否已被统一染成某种颜色)。 当存在懒惰标记时,表示整段已经被覆盖为同种颜色,此时可以直接忽略其内部细节而仅保留两端的颜色信息。 --- ##### 更新逻辑 在执行路径染色的过程中,需要注意以下几点: 1. 如果目标路径跨越多个重链,则需分别对各部分单独处理; 2. 若遇到带有懒惰标记的节点,在下推之前应先清除原有标签的影响; 3. 合并两个相邻片段的结果时,要特别注意它们之间是否存在边界效应——即两者的末端颜色是否一致会影响最终计算得到的颜色段数量。 下面给出完整的伪代码实现: ```python class SegmentTreeNode: def __init__(self, l=0, r=0): self.l = l # 左边界 self.r = r # 右边界 self.sum = 0 # 颜色段数 self.left_color = None # 左端点颜色 self.right_color = None # 右端点颜色 self.lazy_tag = -1 # 懒惰标记 (-1 表示无) def push_up(node): """合并左右孩子信息""" if node.left_child.right_color == node.right_child.left_color: node.sum = node.left_child.sum + node.right_child.sum - 1 else: node.sum = node.left_child.sum + node.right_child.sum node.left_color = node.left_child.left_color node.right_color = node.right_child.right_color def build_tree(tree, idx, l, r): tree[idx].l = l tree[idx].r = r if l == r: # 初始化叶子结点 tree[idx].left_color = colors[l] tree[idx].right_color = colors[l] tree[idx].sum = 1 return mid = (l + r) >> 1 build_tree(tree, idx * 2, l, mid) build_tree(tree, idx * 2 + 1, mid + 1, r) push_up(tree[idx]) def update_range(tree, idx, L, R, color): """区间 [L,R] 的全部元素改为指定颜色 'color' """ if tree[idx].l >= L and tree[idx].r <= R: # 完全包含的情况 tree[idx].left_color = tree[idx].right_color = color tree[idx].sum = 1 tree[idx].lazy_tag = color return if tree[idx].lazy_tag != -1: # 下传懒惰标记 propagate_lazy(tree, idx) mid = (tree[idx].l + tree[idx].r) >> 1 if L <= mid: update_range(tree, idx * 2, L, R, color) if R > mid: update_range(tree, idx * 2 + 1, L, R, color) push_up(tree[idx]) # 自底向上重新计算父节点信息 def query_colors(tree, idx, L, R): """查询区间 [L,R] 上的颜色段数""" if tree[idx].l >= L and tree[idx].r <= R: return tree[idx].sum if tree[idx].lazy_tag != -1: # 下传懒惰标记 propagate_lazy(tree, idx) res = 0 mid = (tree[idx].l + tree[idx].r) >> 1 if L <= mid: res += query_colors(tree, idx * 2, L, R) if R > mid: res += query_colors(tree, idx * 2 + 1, L, R) return res def propagate_lazy(tree, idx): """传播懒惰标记至子节点""" lazy_val = tree[idx].lazy_tag if lazy_val != -1: tree[idx * 2].left_color = tree[idx * 2].right_color = lazy_val tree[idx * 2].sum = 1 tree[idx * 2].lazy_tag = lazy_val tree[idx * 2 + 1].left_color = tree[idx * 2 + 1].right_color = lazy_val tree[idx * 2 + 1].sum = 1 tree[idx * 2 + 1].lazy_tag = lazy_val tree[idx].lazy_tag = -1 ``` 以上代码展示了如何基于线段树实现在树链剖分框架下的路径染色功能[^4]。 --- #### 总结 通过结合树链剖分与线段树技术,我们可以优雅地解决诸如路径染色等问题。这种方法不仅具有较高的时间效率 (\(O(\log^2 n)\)) ,而且易于扩展以适应更多复杂的场景需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值