【SDOI2011】【BZOJ2243】【树链剖分】染色

本文详细介绍了一种处理树上动态区间操作问题的有效方法——链剖算法,并通过一个具体题目来展示其实现过程。文章首先介绍了算法的基本概念,然后逐步解析了如何利用DFS预处理、倍增法求最近公共祖先等技巧进行链剖,并最终实现树上区间染色及颜色段查询的操作。

从2.16开坑学链剖,假期颓废无止境回来之后还要天天测试所以一直拖到现在做完了第一个题
话说是不是直接做QT比较好毕竟看起来友好一些这个题的状态实在有些蛋疼
(P.S.我的链剖跟黄学长学的所以写起来和网上的不太一样看起来会很SXBK)
Description

给定一棵有n个节点的无根树和m个操作,操作有2类:
1、将节点a到节点b路径上所有点都染成颜色c;
2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段),如“112221”由3段组成:“11”、“222”和“1”。
请你写一个程序依次完成这m个操作。
Input

第一行包含2个整数n和m,分别表示节点数和操作数;
第二行包含n个正整数表示n个节点的初始颜色
下面 行每行包含两个整数x和y,表示x和y之间有一条无向边。
下面 行每行描述一个操作:
“C a b c”表示这是一个染色操作,把节点a到节点b路径上所有点(包括a和b)都染成颜色c;
“Q a b”表示这是一个询问操作,询问节点a到节点b(包括a和b)路径上的颜色段数量。
Output

对于每个询问操作,输出一行答案。
Sample Input

6 5

2 2 1 2 1 1

1 2

1 3

2 4

2 5

2 6

Q 3 5

C 2 1 1

Q 3 5

C 5 1 2

Q 3 5

Sample Output

3

1

2

HINT

数N105,操作数M105,所有的颜色C为整数且在[0, 109]之间。

Source

第一轮day1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define MAXINT 0x7fffffff
#define MAXN 100010
#define lchild rt<<1,l,mid
#define rchild rt<<1|1,mid+1,r
#define ln rt<<1
#define rn rt<<1|1
using namespace std;
int n,m;
int top,tp;
int co[MAXN];
int a,b,color;
int deep[MAXN],size[MAXN],chain[MAXN],fa[MAXN][18],num[MAXN];
//deep 深度,size 子树大小,chain 接的链,fa 父亲节点,num 点的编号 
bool vis[MAXN];//节点是否已经访问过 
char ch[2];
struct seg
{
    int mark;//染色标记 
    int l,r;//对应左右区间 
    int data;//颜色段数量 
    int lc,rc;//区间左右端点颜色 
}tree[MAXN*4];
struct edge
{
    int to;
    edge *next;
}e[MAXN*2],*prev[MAXN];
void insert(int u,int v)
{
    e[++top].to=v;
    e[top].next=prev[u];
    prev[u]=&e[top];
}
void dfs1(int x)//第一遍DFS处理子树大小/祖先关系/深度 
{
    size[x]=1;
    vis[x]=1;
    for (int i=1;i<=17;i++)
    {
        if (deep[x]<(1<<i)) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];//倍增处理祖先 
    }
    for (edge *i=prev[x];i;i=i->next)
    {
        int t=i->to;
        if (vis[t]) continue;
        deep[t]=deep[x]+1;
        fa[t][0]=x;
        dfs1(t);
        size[x]+=size[t];
    }
}
void dfs2(int x,int last)//接链上编号.last为之前的链 
{
    num[x]=++tp;
    chain[x]=last;//x为当前重子节点 
    int t=0;
    for (edge *i=prev[x];i;i=i->next)
        if (deep[i->to]>deep[x]&&size[t]<size[i->to])//寻找重子节点 
            t=i->to;
    if (!t) return;
    dfs2(t,last);
    for (edge *i=prev[x];i;i=i->next)
        if (deep[i->to]>deep[x]&&i->to!=t)
            dfs2(i->to,i->to);//在轻子节点上重新接出新的链 
}
void push_up(int rt)
{
    tree[rt].lc=tree[ln].lc;tree[rt].rc=tree[rn].rc;
    tree[rt].data=tree[ln].data+tree[rn].data;
    if (tree[ln].rc==tree[rn].lc) tree[rt].data--;//左右区间相接处
    //颜色相同的话需要把data减一 
}
void push_down(int rt)
{
    if (tree[rt].mark==-MAXINT) return;
    if (tree[rt].l==tree[rt].r) return;
    tree[ln].data=tree[rn].data=1;
    tree[ln].mark=tree[rn].mark=tree[ln].lc=tree[rn].lc=tree[ln].rc=tree[rn].rc=tree[rt].mark;
    tree[rt].mark=-MAXINT;
}
void build(int rt=1,int l=1,int r=n)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].data=1;
    tree[rt].mark=-MAXINT;
    if (l==r) return;
    int mid=(l+r)>>1;
    build(lchild);
    build(rchild);
    //push_up(rt);
}
int lca(int a,int b)//最近公共祖先.将链提到最近公共祖先上 
{
    if (deep[a]<deep[b]) swap(a,b);
    int t=deep[a]-deep[b];
    for (int i=0;i<=17;i++)
        if (t&(1<<i)) a=fa[a][i];
    for (int i=17;i>=0;i--)
        if (fa[a][i]!=fa[b][i]) 
        {
            a=fa[a][i];
            b=fa[b][i];
        }
    if (a==b) return a;
    else return fa[a][0];
}
void modify(int rt,int l,int r,int col)//修改区间颜色 
{
    push_down(rt);
    int L=tree[rt].l,R=tree[rt].r;
    if (L==l&&R==r)
    {
        tree[rt].data=1;
        tree[rt].lc=tree[rt].rc=col;
        tree[rt].mark=col;
        return;
    }
    int mid=(L+R)>>1;
    if (r<=mid) modify(ln,l,r,col);
    else
    if (l>mid) modify(rn,l,r,col);
    else
    {
        modify(ln,l,mid,col);
        modify(rn,mid+1,r,col);
    }
    push_up(rt);
}
void Modify(int a,int b,int col)//修改两点间路径颜色 
{
    while(chain[a]!=chain[b])
    {
        modify(1,num[chain[a]],num[a],col);
        a=fa[chain[a]][0];
    }
    modify(1,num[b],num[a],col);//在把链上提之后a在b的左侧
    //(差不多就是这个意思自己懂就行...) 
}
int query(int rt,int l,int r)//查询区间颜色段数目 
{
    push_down(rt);
    int L=tree[rt].l,R=tree[rt].r;
    if (L==l&&R==r) return tree[rt].data;
    int mid=(L+R)>>1;
    if (r<=mid) return query(ln,l,r);
    else
    if (mid<l) return query(rn,l,r);
    else
    {
        if (tree[ln].rc==tree[rn].lc)
            return query(ln,l,mid)+query(rn,mid+1,r)-1;
        else
            return query(ln,l,mid)+query(rn,mid+1,r);
    }
}
int pointcolor(int rt,int x)//查询某个点的颜色 
{
    push_down(rt);
    int L=tree[rt].l,R=tree[rt].r;
    if (L==R) return tree[rt].lc;
    int mid=(L+R)>>1;
    if (x<=mid) return pointcolor(ln,x);
    else return pointcolor(rn,x);
}
int Query(int a,int b)//查询两点间路径颜色段数目 
{
    int ret=0;
    while (chain[a]!=chain[b])
    {
        ret+=query(1,num[chain[a]],num[a]);
        if (pointcolor(1,num[chain[a]])==pointcolor(1,num[fa[chain[a]][0]]))
            ret--;
        a=fa[chain[a]][0];
    }
    ret+=query(1,num[b],num[a]);
    return ret;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        scanf("%d",&co[i]);
    for (int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        insert(a,b);insert(b,a);
    }
    dfs1(1);
    dfs2(1,1);
    build();
    for (int i=1;i<=n;i++)
        modify(1,num[i],num[i],co[i]);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",ch);
        if (ch[0]=='Q')
        {
            scanf("%d%d",&a,&b);
            int t=lca(a,b);
            printf("%d\n",Query(a,t)+Query(b,t)-1);
        }
        else
        {
            scanf("%d%d%d",&a,&b,&color);
            int t=lca(a,b);
            Modify(a,t,color);
            Modify(b,t,color);
        }
    }
}

能把代码写的这么沙茶我也真是弱求不喷

### 树链剖分的适用场景与使用方法 树链剖分是一种高效的数据结构,用于处理树上的路径查询和修改问题。它通过将树分解为若干条不相交的链来优化复杂度,使得许多原本需要 \(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)\),适合处理大规模数据。 - 在实际应用中,需要根据问题的具体需求选择合适的剖分方式(如重链剖分或长链剖分)。 - 结合其他数据结构(如线段树、树状数组)可以进一步提升效率。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值