【bzoj1984】【坑】月下“毛景树” 树链剖分

本文介绍了一种基于树的分治算法实现方法,通过构建数据结构来解决毛毛虫在树形路径上的操作和查询问题,包括更改、增加路径上的数值及查询最大值等。

Description

毛毛虫经过及时的变形,最终逃过的一劫,离开了菜妈的菜园。 毛毛虫经过千山万水,历尽千辛万苦,最后来到了小小的绍兴一中的校园里。爬啊爬~爬啊爬~~ 毛毛虫爬到了一颗小小的“毛景树”下面,发现树上长着他最爱吃的毛毛果~~~ “毛景树”上有N个节点和N-1条树枝,但节点上是没有毛毛果的,毛毛果都是长在树枝上的。但是这棵“毛景树”有着神奇的魔力,他能改变树枝上毛毛果的个数:  Change k w:将第k条树枝上毛毛果的个数改变为w个。  Cover u v w:将节点u与节点v之间的树枝上毛毛果的个数都改变为w个。  Add u v w:将节点u与节点v之间的树枝上毛毛果的个数都增加w个。 由于毛毛虫很贪,于是他会有如下询问:  Max u v:询问节点u与节点v之间树枝上毛毛果个数最多有多少个。
Input

第一行一个正整数N。 接下来N-1行,每行三个正整数Ui,Vi和Wi,第i+1行描述第i条树枝。表示第i条树枝连接节点Ui和节点Vi,树枝上有Wi个毛毛果。 接下来是操作和询问,以“Stop”结束。
Output

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

4

1 2 8

1 3 7

3 4 9

Max 2 4

Cover 2 4 5

Add 1 4 10

Change 1 16

Max 2 4

Stop

Sample Output

9

16

【Data Range】

1<=N<=100,000,操作+询问数目不超过100,000。

保证在任意时刻,所有树枝上毛毛果的个数都不会超过10^9个。

HINT

Source

树的分治


bzoj不让我交!!!

我和【submit】这个键战斗了无数次!!

然后每次都【无法显示该网页】!!

挖坑!!!

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int SZ = 400010;
const int INF = 1000000010;

int head[SZ],nxt[SZ],tot = 0,to[SZ];

void build_edge(int f,int t)
{
    to[++ tot] = t;
    nxt[tot] = head[f];
    head[f] = tot;
}

int top[SZ],deep[SZ],sz[SZ],son[SZ],fa[SZ],inseg[SZ];

void dfs_1(int u,int f)
{
    fa[u] = f;
    sz[u] = 1;
    deep[u] = deep[f] + 1;
    for(int i = head[u];i;i = nxt[i])
    {
        int v = to[i];
        if(v == f) continue;
        dfs_1(v,u);
        sz[u] += sz[v];
        if(!son[u] || sz[son[u]] < sz[v]) son[u] = v;
    }
}

int totp = 0;

void dfs_2(int u,int tp)
{
    inseg[u] = ++ totp;
    top[u] = tp;
    if(!son[u])  return;
    dfs_2(son[u],tp);

    for(int i = head[u];i;i = nxt[i])
    {
        int v = to[i];
        if(v == fa[u] || v == son[u]) continue;
        dfs_2(v,v);
    }
}

struct segment{
    int l,r;
    int add,maxn,c;
}tree[SZ << 2];

void update(int p)
{
    tree[p].maxn = max(tree[p << 1].maxn,tree[p << 1 | 1].maxn);
}

void build_seg(int p,int l,int r)
{
    tree[p].l = l; tree[p].r = r;
    if(l == r)
    {
        tree[p].maxn = tree[p].add = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build_seg(p << 1,l,mid);
    build_seg(p << 1 | 1,mid + 1,r);
    update(p);
}

void spread(int p)
{
    if(tree[p].c)
    {
        tree[p << 1].c = tree[p << 1 | 1].c = tree[p].c;
        tree[p << 1].maxn = tree[p << 1 | 1].maxn = tree[p].c;
        tree[p << 1].add = tree[p << 1 | 1].add = 0;
        tree[p].c = 0;
    }
    if(tree[p].add)
    {
        tree[p << 1].maxn += tree[p].add;   tree[p << 1 | 1].maxn += tree[p].add;
        if(tree[p << 1].c) tree[p << 1].c += tree[p].add;
        else tree[p << 1].add += tree[p].add;
        if(tree[p << 1 | 1].c) tree[p << 1 | 1].c += tree[p].add;
        else tree[p << 1 | 1].add += tree[p].add;
        tree[p].add = 0;
    }
}

void change(int p,int l,int r,int d)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        tree[p].add = 0;
        tree[p].c = tree[p].maxn = d;
        return;
    }
    spread(p);
    int mid = (tree[p].l + tree[p].r) >> 1;
    if(l <= mid) change(p << 1,l,r,d);
    if(mid < r) change(p << 1 | 1,l,r,d);
    update(p);
}

void add(int p,int l,int r,int d)
{
    if(l <= tree[p].l && tree[p].r <= r)
    {
        if(tree[p].c) tree[p].c += d;
        else tree[p].add += d;
        tree[p].maxn += d;
        return;
    }
    spread(p);
    int mid = (tree[p].l + tree[p].r) >> 1;
    if(l <= mid) add(p << 1,l,r,d);
    if(mid < r) add(p << 1 | 1,l,r,d);
    update(p);
}

int ask(int p,int l,int r)
{
    if(l <= tree[p].l && tree[p].r <= r)
        return tree[p].maxn;
    spread(p);
    int mid = (tree[p].l + tree[p].r) >> 1;
    int ans = 0;
    if(l <= mid) ans = max(ans,ask(p << 1,l,r));
    if(mid < r) ans = max(ans,ask(p << 1 | 1,l,r));
    return ans;
}

void find_change(int x,int y,int d)
{
    int fx = top[x],fy = top[y];
    while(fx != fy)
    {
        if(deep[fx] < deep[fy]) swap(x,y),swap(fx,fy);
        change(1,inseg[fx],inseg[x],d);
        x = fa[fx]; fx = top[x];
    }
    if(x != y)
    {
        if(deep[x] > deep[y]) swap(x,y);
        change(1,inseg[x] + 1,inseg[y],d);      
    }
}

void find_add(int x,int y,int d)
{
    int fx = top[x],fy = top[y];
    while(fx != fy)
    {
        if(deep[fx] < deep[fy]) swap(x,y),swap(fx,fy);
        add(1,inseg[fx],inseg[x],d);
        x = fa[fx]; fx = top[x];
    }
    if(x != y)
    {
        if(deep[x] > deep[y]) swap(x,y);
        add(1,inseg[x] + 1,inseg[y],d);     
    }
}

int find_ans(int x,int y)
{
    int fx = top[x],fy = top[y];
    int ans = 0;
    while(fx != fy)
    {
        if(deep[fx] < deep[fy]) swap(x,y),swap(fx,fy);
        ans = max(ans,ask(1,inseg[fx],inseg[x]));
        x = fa[fx]; fx = top[x];
    }
    if(x != y)
    {
        if(deep[x] > deep[y]) swap(x,y);
        ans = max(ans,ask(1,inseg[x] + 1,inseg[y]));        
    }
    return ans;
}

struct edge{
    int f,t,d;
}l[SZ];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i = 1;i < n;i ++)
    {
        scanf("%d%d%d",&l[i].f,&l[i].t,&l[i].d);
        build_edge(l[i].f,l[i].t); 
        build_edge(l[i].t,l[i].f); 
    }

    dfs_1(1,0); dfs_2(1,1);
    build_seg(1,1,n);

    for(int i = 1;i < n;i ++)
    {
        if(deep[l[i].f] > deep[l[i].t]) swap(l[i].f,l[i].t);
        change(1,inseg[l[i].t],inseg[l[i].t],l[i].d);
    }


    char opt[10];
    while(~scanf("%s",opt) && opt[1] != 't')
    {
        if(opt[1] == 'h')
        {
            int k,w;
            scanf("%d%d",&k,&w);
            change(1,inseg[l[k].t],inseg[l[k].t],w);
        }
        else if(opt[1] == 'o')
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            find_change(u,v,w);
        }
        else if(opt[1] == 'd')
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            find_add(u,v,w);            
        }
        else if(opt[1] == 'a')
        {
            int u,v;
            scanf("%d%d",&u,&v);
            printf("%d\n",find_ans(u,v));               
        }
    //  for(int i = 1;i <= n;i ++) printf("%d ",find_ans(i,i)); puts("");
    }
    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] > 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)\),适合处理大规模数据。 - 在实际应用中,需要根据问题的具体需求选择合适的剖分方式(如重链剖分或长链剖分)。 - 结合其他数据结构(如线段状数组)可以进一步提升效率。 ---
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值