树链剖分学习

解决的问题

对于给出的树上两点求之间的最值或者更新操作变为logn。

其他方法

Tarjan求LCA的复杂度为 O(N+Q)所以不断更新复杂度太高。



本质: 就是将树划分为不重合的多条链每条链都有一个线段树中的编号(可类比dfs序转换线段树的想法)+线段树。在求的过程中根据重链不断逼近再用线段树维护即可。

入门文章

练习题目

一般有对点建树和对边建树两种方案,看题目要求。对第几条边进行操作这种情况要将边存储下来。

其实就是一个点的影响范围。线段树的话更新要更新到单点

模板

#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define  inf  0x3f3f3f3f
#define  maxn (100+100000)
using namespace std;
typedef long long ll;

struct Edge{
    int u,to,next;
}edge[maxn*2];
int head[maxn],tot;
int top[maxn];///top[v] 表示以 v所在的重链顶端节点
int fa[maxn]; /// 父亲结点
int deep[maxn];/// 深度
int num[maxn];/// num[v] 表示以v为根的子树节点数
int p[maxn];/// p[v] 表示v与其父亲结点的连边在线段树中的位置   PS:这个应该是对边进行剖分
int fp[maxn];///和p相反
int son[maxn];///重儿子
int pos;///线段树总数
void init(){
    tot = 0;
    memset(head,-1,sizeof(head));
    pos = 0;///
    memset(son,-1,sizeof(son));
}

void addedge(int u ,int v ){
    edge[tot].to = v;
    edge[tot].next = head[u];
    head[u] = tot++;
}
void dfs1(int u,int pre,int d){ ///第一遍求出fa,deep,num,son
    deep[u] = d;
    fa[u] = pre;
    num[u] = 1;
    for(int i = head[u];~i;i = edge[i].next){
        int v = edge[i].to;
        if(v==pre) continue;
        dfs1(v,u,d+1);
        num[u] += num[v];
        if(son[u]==-1 || num[v] > num[son[u]])
            son[u] = v;
    }
}
///这里没有传入pre这个参数而是用fa ,是因为fa在后面有用,这样也无妨
void dfs2(int u,int sp){ /// 第二遍求出top,p,fp
    top[u] = sp;
    p[u] = pos++;
    fp[p[u]] = u;
    if(son[u]==-1)
        return;
    dfs2(son[u],sp);
    for(int i = head[u];~i;i=edge[i].next){
        int v = edge[i].to;
        if(v != son[u] && v!= fa[u])
            dfs2(v,v);
    }
}
struct Node{
    int l,r;
    ll Max;
}segTree[maxn*3];
void push_up(int i) {
    segTree[i].Max = segTree[i<<1].Max + segTree[(i<<1)|1].Max;
    ///segTree[i].Max = min(segTree[i<<1].Max , segTree[(i<<1)|1].Max);
}
 void build(int i,int l,int r)
 {
     segTree[i].l = l;
     segTree[i].r = r;
     segTree[i].Max = 0;
     if(l == r)return;
     int mid = (l+r)/2;
     build(i<<1,l,mid);
     build((i<<1)|1,mid+1,r);
 }

void update(int i,int k,int val) { /// 更新线段树的第k个值为val
     if(segTree[i].l == k && segTree[i].r == k) {
         segTree[i].Max = val;
         return;
     }
     int mid = (segTree[i].l + segTree[i].r)/2;
     if(k <= mid)update(i<<1,k,val);
     else update((i<<1)|1,k,val);
     push_up(i);
}
 ll query(int i,int l,int r) { ///查询线段树中[l,r] 的最大值

     if(segTree[i].l >= l && segTree[i].r <= r) /// 这个写法和平常不一样是因为查询区间必然是重链上的点
         return segTree[i].Max;
     int mid = (segTree[i].l + segTree[i].r)/2;
     ll ans = 0;
     if(r <= mid) ans = query(i<<1,l,r);
     else if(l > mid)  ans = query((i<<1)|1,l,r);
     else {
        ans = query(i<<1,l,mid);
        ans += query((i<<1)|1,mid+1,r);
     }
     return ans;
}

ll find(int u,int v){ /// 查询u->v边的最大值
    int f1 = top[u],f2 = top[v];
    ll tmp = 0;
    while(f1 != f2){
        if(deep[f1] < deep[f2])
            swap(f1,f2),swap(u,v);
        tmp = tmp + query(1,p[f1],p[u]);
        u = fa[f1]; f1 = top[u];
    }
    if(u==v) return tmp;///当以点剖分的时候不要这句因为点要算入,边的话就不算入。这里是用边
    if(deep[u] > deep[v] ) swap(u,v);
    return tmp + query(1,p[son[u]],p[v]); ///这个只到u所以用它孩子的连线
}

int e[maxn][3];
int main(){
    int T,cas=1;
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        init();
        for(int i = 1;i<n;i++){
            scanf("%d%d%d",&e[i][0],&e[i][1],&e[i][2]);
            addedge(e[i][0],e[i][1]);
            addedge(e[i][1],e[i][0]);
        }
        dfs1(1,0,0);
        dfs2(1,1);
        build(1,0,pos-1);
        for(int i = 1;i<n;i++){
            if(deep[e[i][0]] > deep[e[i][1]])
                swap(e[i][0],e[i][1]);
            update(1,p[e[i][1]],e[i][2]); ///刚开始第三个参数写成这个p[e[i][2]]错了,这个这样写是因为存放边值
        }
        for(int i = 0;i<m;i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            if(a) printf("%I64d\n",find(b,c));
            else update(1,p[e[b][1]],c);///更新边还是点 点的话就是p[u] 第几条边就是 e[u][1]
        }//这句写错了wa好久
    }
    return 0;
}
时间: 2024-10-15 20:21:26

树链剖分学习的相关文章

树链剖分学习笔记

先让我们看一个题目 有一棵n个节点的树,树的每条边有个边权,有如下两种操作 1.修改一条边的边权 2.查询两点之间路径的权值 对于这种题目,可能有人会选择直接暴力,这很明显不行. 换一种思路,如果我们把树的每一条边拆下来,对他们进行编号,然后使用线段树来存储呢?使用线段树来对每条边的边权进行修改和查询是很方便的.于是这样我们就引出了树链剖分. 树链剖分其实就是把一棵树上的各个边拆开来进行处理,从而对树的整体进行划分. 将树划分为链,用数据结构来维护这些链,时间复杂度大致为O(log n) . 在

树链剖分学习记录

HDU  3966 基于点权 修改(增减)一条路径上的点  单结点查询 code: #pragma comment(linker, "/STACK:1024000000,1024000000") #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 50010; struct Edge{ int to, next; }e

树链剖分 学习笔记

像我这么懒肯定不会有图辣 在GCZ大爷的安利下来学树剖,慢慢补吧... 前言 这几天边看<高级数据结构>,边看各路神犇的Blog,终于看懂一点点了.不得不说那本书作者的码风是真的垃圾. 树链剖分,是一种将树剖分成多条不相交的链的算法,并通过其他的数据结构来维护这些链上的信息. 最简单的例子就是LCA,假设现在有一棵退化成链的树.如果要求任意两点的LCA,因为他们在同一条链上的缘故,只需要判断一下两者的深度就行了.由此可见,在链上是比在树上更好操作的. 那么该怎么将一棵树剖分开来捏? 先搬出一堆

树链剖分学习&amp;BZOJ1036

题目传送门 树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组.SBT.SPLAY.线段树等)来维护每一条链. 以下是几种概念: 常见的路径剖分的方法是轻重树链剖分(启发式剖分) 将树中的边分为:轻边和重边 ?定义size(X)为以X为根的子树的节点个数. ?令V为U的儿子节点中size值最大的节点,那么边(U,V)被称为重边,树中重边之外的边被称为轻边. 性质:?轻边(U,V),size(V)<=size

[学习笔记]树链剖分

基本思想 树链剖分一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每条边属于且只属于一条链,然后再通过数据结构来维护每一条链. 一些定义 树链:树上的路径. 剖分:把路径分类为重链和轻链. 重儿子:u的子节点中siz[v]值最大的v. 轻儿子:u的其它子节点. 重边:点u与其重儿子的连边. 轻边:点u与其轻儿子的连边. 重链:由重边连成的路径. 轻链:轻边. 性质 如果(u,v)为轻边,则siz[v]$\times$2<siz[u]. 从根到某一点的路径上轻链.重链的个数都不大于l

HYSBZ 1036 【树链剖分】

思路: 裸裸的树链剖分.... 树链剖分就是把一棵树分成若干重链和轻链...然后保证形成的线段树上每条链是连续存储的.然后这样就能用线段树进行维护了. 但是每次一定要保证是在同一条链里边....思路就是这样.... 感觉最近越来越淡定了,题目卡住了也不怎么着急了,慢慢看代码...嗯...有点像学习的感觉了.... 明天青岛理工的邀请赛要好好玩玩... #include<stdio.h> #include<string.h> #include<algorithm> usi

树链剖分专题

学习了一下树链剖分,找了几个有意义的题目训练一下 前4题是基础训练, A.B是AOV树(点记录信息) C.D是AOE树(边记录信息) *注意一下poj好像是提交的代码包含/**/这样的注释会出问题 A.HDU 3966 题意:给你N个点M条边的一棵AOV树,有P次操作 操作分别是:‘I’:将C1到C2路径上的点增加K:     ‘D’:将C1到C2路径上的点减少K:     ‘Q’:问你C点上的权: 解:树链剖分基础题,需要注意的是杭电的服务器是windows的,代码中加入用下面这句话扩栈 #p

树链剖分小结

这两周在学树剖. 先扔个模板 有一类题目,要求实现一类在树上的操作,比如: 修改/求 树上某 节点/边权 的(最)值: 修改/求 树上某 节点/边权 及其子树上所有节点的(最)值: 修改/求 树上某两点路径间的 节点/边权 的(最)值: 乍一看似乎用线段树就可以实现,但是如果仔细想想,可以发现单凭线段树是无法解决的. 对于这类题目,常用的解决方法是树链剖分. 树链剖分(节选自starszys博客): 相关定义: 重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子. 轻儿子:v

【BZOJ】1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树)

第一种做法(时间太感人): 这题我真的逗了,调了一下午,疯狂造数据,始终找不到错. 后来发现自己sb了,更新那里没有打id,直接套上u了.我.... 调了一下午啊!一下午的时光啊!本来说好中午A掉去学习第二种做法,噗 好吧,现在第一种做法是hld+seg+bst+二分,常数巨大,log^4级别,目前只会这种. 树剖后仍然用线段树维护dfs序区间,然后在每个区间建一颗平衡树,我用treap,(这题找最大啊,,,囧,并且要注意,这里的rank是比他大的数量,so,我们在二分时判断要判断一个范围,即要