HYSBZ 2243 染色 树链剖分 点上剖分

本文介绍了一种解决树形结构中路径操作问题的方法:通过树链剖分结合线段树实现高效的路径修改与查询。文章详细阐述了树链剖分的过程,并展示了如何利用线段树进行区间合并来维护路径上颜色段的数量。

题目链接:http://bak2.vjudge.net/problem/38096/origin
题意:
两种操作:
1.修改u到v路径上的点的颜色为c
2.询问u到v路径上有多少段颜色

线段树上用区间合并来维护答案
主要是在询问时要考虑一下,维护上个段的最后节点颜色来维护颜色段数

#include <bits/stdc++.h>
#define sf scanf
#define pf printf

using namespace std;
const int maxn = 100000 + 5;
int n,m;
//边集
struct Edge{
    int v,pre,c;
}Es[maxn * 2];
int head[maxn],TOT_EDGE;
void INIT_EDGE(){memset(head,-1,sizeof head);TOT_EDGE = 0;}
void ADD_EDGE(int u,int v,int c){
    Es[TOT_EDGE].v = v;
    Es[TOT_EDGE].c = c;
    Es[TOT_EDGE].pre = head[u];
    head[u] = TOT_EDGE++;
}

//树链剖分
int son[maxn],fa[maxn],dep[maxn],size[maxn];
int top[maxn],tid[maxn],rank[maxn],SegSize;
int DFS(int rt){
    dep[rt] = dep[fa[rt]] + 1;
    son[rt] = 0;
    size[rt] = 1;
    for(int i = head[rt];~i;i = Es[i].pre){
        int v = Es[i].v;
        if(v != fa[rt]){
            fa[v] = rt;
            size[rt] += DFS(v);
            if(size[son[rt]] < size[v]) son[rt] = v;
        }
    }
    return size[rt];
}
void Split(int rt,int tp){
    top[rt] = tp;
    tid[rt] = ++SegSize;
    rank[tid[rt]] = rt;
    if(son[rt]){
        Split(son[rt],tp);
        for(int i = head[rt];~i;i = Es[i].pre){
            int v = Es[i].v;
            if(v != fa[rt] && v != son[rt]) Split(v,v);
        }
    }
}
void TreeLineSplit(){
    dep[0] = 0;
    size[0] = 0;
    fa[1] = 0;
    DFS(1);
    SegSize = 0;
    Split(1,1);
}

//线段树
#define lson rt << 1 , l , mid
#define rson rt << 1 | 1,mid + 1,r
int LEFT[maxn << 2],RIGHT[maxn << 2],CNT[maxn << 2],num[maxn];
int cover[maxn << 2];
void PushUp(int rt){
    LEFT[rt] = LEFT[rt << 1];
    RIGHT[rt] = RIGHT[rt << 1 | 1];
    CNT[rt] = CNT[rt << 1] + CNT[rt << 1 | 1] - (RIGHT[rt << 1] == LEFT[rt << 1 | 1]);
}
void PushDown(int rt){
    if(cover[rt] != -1){
        LEFT[rt << 1] = RIGHT[rt << 1] = cover[rt];
        LEFT[rt << 1 | 1] = RIGHT[rt << 1 | 1] = cover[rt];
        CNT[rt << 1] = CNT[rt << 1 | 1] = 1;
        cover[rt << 1] = cover[rt << 1 | 1] = cover[rt];
        cover[rt] = -1;
    }
}
void SegTreeBuild(int rt,int l,int r){
    cover[rt] = -1;
    if(l == r){
        LEFT[rt] = RIGHT[rt] = num[l];
        CNT[rt] = 1;
        return;
    }
    int mid = l + r >> 1;
    SegTreeBuild(lson);SegTreeBuild(rson);
    PushUp(rt);
}
int LEFT_COLOR,RIGHT_COLOR;
int Query(int rt,int l,int r,int L,int R){
    if(L <= l && R >= r){
        if(LEFT_COLOR == -1) LEFT_COLOR = LEFT[rt];
        RIGHT_COLOR = RIGHT[rt];
        return CNT[rt];
    }
    PushDown(rt);
    int mid = l + r >> 1,ret = 0,f = 0;
    if(L <= mid) ret += Query(lson,L,R),f++;
    if(R > mid) ret += Query(rson,L,R),f++;
    if(f == 2 && RIGHT[rt << 1] == LEFT[rt << 1 | 1]) ret--;
    PushUp(rt);
    return ret;
}
void Update(int rt,int l,int r,int L,int R,int c){
    if(L <= l && R >= r){
        cover[rt] = c;
        LEFT[rt] = RIGHT[rt] = c;
        CNT[rt] = 1;
        return;
    }
    PushDown(rt);
    int mid = l + r >> 1;
    if(L <= mid) Update(lson,L,R,c);
    if(R > mid) Update(rson,L,R,c);
    PushUp(rt);
}
int INIT_COLOR[maxn];
void GetNumAr(){
    for(int i = 1;i <= n;++i){
        num[tid[i]] = INIT_COLOR[i];
    }
}


//题目操作
int Ans_Query(int u,int v){
    int ret = 0;
    int preU = -1,preV = -1;
    while(top[u] != top[v]){
        LEFT_COLOR = -1;
        if(dep[top[u]] > dep[top[v]]){
            ret += Query(1,1,n,tid[top[u]],tid[u]);
            if(RIGHT_COLOR == preU){
                ret--;
            }
            preU = LEFT_COLOR;
            u = fa[top[u]];
        }
        else{
            ret += Query(1,1,n,tid[top[v]],tid[v]);
            if(RIGHT_COLOR == preV){
                ret--;
            }
            preV = LEFT_COLOR;
            v = fa[top[v]];
        }
    }
    LEFT_COLOR = -1;
    if(dep[u] < dep[v]){
        ret += Query(1,1,n,tid[u],tid[v]);
        if(RIGHT_COLOR == preV) ret--;
        if(LEFT_COLOR == preU) ret--;
    }
    else{
        ret += Query(1,1,n,tid[v],tid[u]);
        if(RIGHT_COLOR == preU) ret--;
        if(LEFT_COLOR == preV) ret--;

    }
    return ret;
}

void CHANGE(int u,int v,int c){
    while(top[u] != top[v]){
        if(dep[top[v]] < dep[top[u]]) swap(u,v);
        Update(1,1,n,tid[top[v]],tid[v],c);
        v = fa[top[v]];
    }
    if(dep[v] < dep[u]) swap(u,v);
    Update(1,1,n,tid[u],tid[v],c);
}
char op[10];
int main(){
//    freopen("rand.txt","r",stdin);
    while( ~sf("%d%d",&n,&m) ){
        INIT_EDGE();
        for(int i = 1;i <= n;++i) sf("%d",&INIT_COLOR[i]);
        for(int i = 1;i < n;++i){
            int u,v;sf("%d%d",&u,&v);
            ADD_EDGE(u,v,0);
            ADD_EDGE(v,u,0);
        }
        TreeLineSplit();
        GetNumAr();
        SegTreeBuild(1,1,n);
        LEFT_COLOR = -1;
        while(m--){
            int x,y,z;
            sf("%s",op);
            if(op[0] == 'Q'){
                sf("%d%d",&x,&y);
                pf("%d\n",Ans_Query(x,y));
            }
            else{
                sf("%d%d%d",&x,&y,&z);
                CHANGE(x,y,z);
            }
        }
    }
}
### 树链剖分中的染色算法实现 树链剖分是一种高效的树形结构优化方法,常用于处理路径和子树上的复杂查询与修改操作。对于涉及染色的操作,通常可以通过树链剖分配合线段树或其他支持区间更新的数据结构来完成。 #### 基本原理 树链剖分的核心思想是将树解为多条重链,并通过 `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)\)) ,而且易于扩展以适应更多复杂的场景需求。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值