树链刨分 (FZU 2082过路费)

本文详细介绍了树链剖分的概念及其实现方法,包括重儿子、轻儿子等关键概念的定义,通过两次DFS求得重链的过程,以及如何利用线段树进行路径上的修改和查询操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  树链刨分:
    在一棵树上进行路径的修改,求和,取极值,数据量较小的情况,我们使用LCA就可以,但是数据量大的时候,我们就需要使用线段树了,但是如何使用,这就需要貌似高级的复杂算法-----树链刨分。
    简单来说就是将一个树的每一个节点都分配到一个重链中,将这些重链收尾连接,变成线性结构,然后使用线段数进行求解。
    介绍几个概念:
    记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的重链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),rush[v]表示v处在线性结构的位置。
    重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
    轻儿子:v的其它子节点。
    重边:点v与其重儿子的连边。
    轻边:点v与其轻儿子的连边。
    重链:由重边连成的路径。
可以通过两个dfs求得重链:
int dfs1(int v,int fas){
  fa[v]=fas;
  if (fas==0) siz[v]=po[v].size();
  else siz[v]=po[v].size()-1;
  if (fas==0) dep[v]=1;
  else dep[v]=dep[fas]+1;
  for (int i=0;i<po[v].size();i++){
    if (po[v][i].x!=fas){
      vis[po[v][i].z]=po[v][i].x;
      dfs1(po[v][i].x,v);
      if (son[v]==0||siz[son[v]]<siz[po[v][i].x]){
         son[v]=po[v][i].x;
    }
    }
  }
  return 0;
}
通过dfs1求得了fa,son,dep,siz;
int dfs2(int v,int topp){
  rush[v]=++totl;
  top[v]=topp;
  if (fa[v]==0) num[totl]=0;
  else {
    for (int i=0;i<po[v].size();i++)
      if (po[v][i].x==fa[v]){
        num[totl]+=po[v][i].v;
        break;
      }
  }
通过dfs2求得了rush,top;
修改操作:例如将u到v的路径上每条边的权值都加上某值x。
    记f1 = top[u],f2 = top[v]。
    当f1 <> f2时:不妨设dep[f1] >= dep[f2],修改区间线性结构上的【rush【u】,rush【f1】】区间,并使u = fa[f1]。
    当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,修改区间线性结构上的【rush【u】,rush【v】】区间,否则修改完成;
 重复上述过程,直到修改完成。
  求和、求极值操作:类似修改操作,但是不更新区间值,而是对其求和、求极值。

举个例子:

对于上图当要修改11到10的路径时。
    第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
    第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
    第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
    第四次迭代:f1 = f2且u = v,修改结束。
下面是一道树链刨分基础题:
 Problem 2082 过路费

Accept: 129    Submit: 548
Time Limit: 1000 mSec    Memory Limit : 32768 KB

 Problem Description

有n座城市,由n-1条路相连通,使得任意两座城市之间可达。每条路有过路费,要交过路费才能通过。每条路的过路费经常会更新,现问你,当前情况下,从城市a到城市b最少要花多少过路费。

 Input

有多组样例,每组样例第一行输入两个正整数n,m(2 <= n<=50000,1<=m <= 50000),接下来n-1行,每行3个正整数a b c,(1 <= a,b <= n , a != b , 1 <= c <= 1000000000).数据保证给的路使得任意两座城市互相可达。接下来输入m行,表示m个操作,操作有两种:一. 0 a b,表示更新第a条路的过路费为b,1 <= a <= n-1 ; 二. 1 a b , 表示询问a到b最少要花多少过路费。

 Output

对于每个询问,输出一行,表示最少要花的过路费。

 Sample Input

2 31 2 11 1 20 1 21 2 1

 Sample Output

12

  解题方法:
    由于n与m比较大,所以应该使用类似与线段树的数据结构,但是线段树只能解决线性问题,所以使用树链刨分。通过树链刨分,我们可以解决求树上两个节点之间路径修改,求和的问题。
    我的代码:
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define maxn 100005
using namespace std;
struct point {
  int x;
  int v;
  int z;
};
vector <point>  po[maxn];
int fa[maxn],siz[maxn],son[maxn],dep[maxn],top[maxn],rush[maxn],totl;
int n,m;
int num[maxn];
int vis[maxn];
int tree[maxn*3];
int dfs1(int v,int fas){
  fa[v]=fas;
  if (fas==0) siz[v]=po[v].size();
  else siz[v]=po[v].size()-1;
  if (fas==0) dep[v]=1;
  else dep[v]=dep[fas]+1;
  for (int i=0;i<po[v].size();i++){
    if (po[v][i].x!=fas){
      vis[po[v][i].z]=po[v][i].x;
      dfs1(po[v][i].x,v);
      if (son[v]==0||siz[son[v]]<siz[po[v][i].x]){
         son[v]=po[v][i].x;
    }
    }
  }
  return 0;
}
int dfs2(int v,int topp){
  rush[v]=++totl;
  top[v]=topp;
  if (fa[v]==0) num[totl]=0;
  else {
    for (int i=0;i<po[v].size();i++)
      if (po[v][i].x==fa[v]){
        num[totl]+=po[v][i].v;
        break;
      }
  }
  //cout<<v<<" "<<fa[v]<<" "<<totl<<" "<<num[totl]<<endl;
  if (son[v]!=0){
      dfs2(son[v],topp);
      for (int i=0;i<po[v].size();i++){
        if (po[v][i].x!=fa[v]&&po[v][i].x!=son[v]){
          dfs2(po[v][i].x,po[v][i].x);
        }
      }
  }
  return 0;
}
int build (int o,int L,int R){
  int M=L+(R-L)/2;
  if (L==R) tree[o]=num[L];
  else {
    build (o*2,L,M);
    build (o*2+1,M+1,R);
    tree[o]=tree[o*2]+tree[o*2+1];
  }
  return 0;
}
int yy,d;
int add(int o,int L,int R){
  if (L==R&&L==yy){
    tree[o]=d;
    num[L]=d;
  }
  else {
    int M=L+(R-L)/2;
    if (yy<=M) add(o*2,L,M);
    if (yy>M) add(o*2+1,M+1,R);
    tree[o]=tree[o*2]+tree[o*2+1];
  }
  return 0;
}
int _sum,y1,y2;
int query(int o,int L,int R){
  if (y1<=L&&y2>=R){
    _sum+=tree[o];
  }
  else {
    int M=L+(R-L)/2;
    if (y1<=M) query(o*2,L,M);
    if (y2>M) query(o*2+1,M+1,R);
  }
  return 0;
}
int print(int v,int u){
  int ans=0;
  while (v!=u){
    int f1=top[v],f2=top[u];
    if (f1==f2){
      _sum=0;
      y1=rush[v];y2=rush[u];
      if (y1>y2){
        int dddf=y1;
        y1=y2;y2=dddf;
      }
      query(1,1,n);
      ans+=_sum;
      if (dep[v]>dep[u]) ans-=num[rush[u]];
      else ans-=num[rush[v]];
      break;
    }
    else {
      if (dep[f1]>dep[f2]){
         y1=rush[v];y2=rush[f1];
         if (y1>y2){
           int dddf=y1;
           y1=y2;y2=dddf;
         }
         _sum=0;
         query(1,1,n);
         ans+=_sum;
         v=fa[f1];
      }
      else if (dep[f1]<dep[f2]){
        y1=rush[u];y2=rush[f2];
         if (y1>y2){
           int dddf=y1;
           y1=y2;y2=dddf;
         }
         _sum=0;
         query(1,1,n);
         ans+=_sum;
         u=fa[f2];
      }
      else {
        y1=rush[v];y2=rush[f1];
         if (y1>y2){
           int dddf=y1;
           y1=y2;y2=dddf;
         }
         _sum=0;
         query(1,1,n);
         ans+=_sum;
         v=fa[f1];
         y1=rush[u];y2=rush[f2];
         if (y1>y2){
           int dddf=y1;
           y1=y2;y2=dddf;
         }
         _sum=0;
         query(1,1,n);
         ans+=_sum;
         u=fa[f2];
      }
    }
  }
  printf("%d\n",ans);
  return 0;
}
int main (){
  //freopen("test.in","r",stdin);
  while (~scanf("%d%d",&n,&m)){
    memset(fa,0,sizeof(fa));
    memset(siz,0,sizeof(siz));
    memset(son,0,sizeof(son));
    memset(dep,0,sizeof(dep));
    memset(top,0,sizeof(top));
    memset(num,0,sizeof(num));
    memset(rush,0,sizeof(rush));
    memset(vis,0,sizeof(vis));
    for (int i=1;i<=n;i++) po[i].clear();
    for (int i=1;i<=n-1;i++){
      int a,b,c;
      scanf("%d%d%d",&a,&b,&c);
      point e;e.x=b;e.v=c;e.z=i;
      po[a].push_back(e);
      e.x=a;
      po[b].push_back(e);
    }
    dfs1(1,0);
    totl=0;
    dfs2(1,1);
    build(1,1,n);
    for (int i=0;i<m;i++){
      int a,b,c;scanf("%d%d%d",&a,&b,&d);
      if (a==0){
        yy=rush[vis[b]];
        add(1,1,n);
      }
      else if (a==1){
         print(b,d);
      }
    }
    /*cout<<"top  ";
    for (int i=1;i<=n;i++){
      cout<<top[i]<<" ";
    }
    cout<<endl;
    cout<<"rush  ";
    for (int i=1;i<=n;i++){
      cout<<rush[i]<<" ";
    }
    cout<<endl;
    cout<<"num  ";
    for (int i=1;i<=n;i++){
      cout<<num[i]<<" ";
    }
    cout<<endl;
    cout<<"dep ";
    for (int i=1;i<=n;i++){
      cout<<dep[i]<<" ";
    }
    cout<<endl;*/
  }
  return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值