树链剖分学习笔记 By cellur925

先%一发机房各路祖传树剖大师%%%。

近来总有人向我安利树剖求LCA,然鹅我还是最爱树上倍增。然鹅又发现近年一些题目(如天天爱跑步、运输计划等在树上进行操作的题目),我有把树转化为一条链求解的思路,但是不知道怎么实现。于是还是学了树链剖分(真香),就权当打暴力的工具了。其实主要是学习它的思想,而它实际包含的知识(线段树(大多情况用线段树,理论上应该还能用其他数据结构维护)、dfs序与时间戳、树的遍历)比较基础,只要把他们掌握,学习树剖就不难了。讲真树剖可能是我学的最快的知识。


 

主要思想:划分轻重链,把树上的某条路径化为一条链,再用数据结构维护。(把一棵树拆成若干互不相交的链)

树剖中的一些概念:

  这里可详见@communist dalao的解释部分

可见,树剖中我们需要记录很多的量,于是就有了我们的第一个核心算法:两遍dfs,求出所有信息!

通常我们第一遍dfs求出d[](深度),f[](父节点),size[](子树大小),son[](重儿子)。

void dfs1(int u,int fa,int dep)
{//第一遍dfs 预处理出f[]/d[]/son[]/size[]
    f[u]=fa;
    d[u]=dep;
    size[u]=1;
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==fa) continue;
        dfs1(v,u,dep+1);
        size[u]+=size[v];
        if(size[v]>size[son[u]])
            son[u]=v;
    }
}

第二遍我们在第一遍的基础上继续求出其他量。top[](当前节点所在链的最顶层节点),id[](节点的新编号,可理解为时间戳),rk[](保存dfs标号在树上的具体节点,实际操作可为点权)

这里id与rk的关系有点像时间戳与dfs序,但不完全是,emm可以感性理解下。(时间戳与dfs序,他们的联系具体在这里探讨过。)

至于为什么要引入他们,因为我们希望一条重链在数据结构(如线段树)上的排列分布是连续的,这样我们才好维护他们。

void dfs2(int u,int t)
{//第二遍dfs 预处理出id[]/rk[]/top[] 
    top[u]=t;
    id[u]=++cnt;
    rk[cnt]=val[u];
    if(!son[u]) return ;//找到了叶子
    //通过优先进入重儿子来保证一条重链上各节点dfs序连续 
    dfs2(son[u],t);
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==son[u]||v==f[u]) continue;
        dfs2(v,v);//在轻链上 top为本身 
    } 
} 

有了这些操作,我们就可以进行线段树的维护了。这里的线段树中,节点标号用的是我们刚映射好的时间戳,权值是节点的点权,重链上的节点在线段树上标号连续。(注意我们的rk数组,建树时用的是它而不是初始点权。)

之后我们就按线段树规矩建树、修改、查询即可。这是一个相对独立的部分。


 

以上便是树链剖分的基本操作,我们以LuoguP3384【模板】树链剖分为例,讲解一下树剖的具体食用方法。

题目要求我们维护:

 

操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z

 

操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和

 

操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z

 

操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

 

慢慢分析。在操作1中,我们首先需要让x,y到同一个重链上,但是在到达之前,我们也需要记录下。于是就有了

 

void treeadd(int x,int y,int w)
{
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        change(1,id[top[x]],id[x],w);
        x=f[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    change(1,id[x],id[y],w);
}

 

操作2原理相似。因为这部分我讲的不是很好==。我是看这位大神的blog看懂的,这里就甩链接了...。

ll treeask(int x,int y)
{
    ll ans=0;
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        (ans+=ask(1,id[top[x]],id[x]))%=moder;
        x=f[top[x]];
    }
    if(d[x]>d[y]) swap(x,y);
    (ans+=ask(1,id[x],id[y]))%=moder;
    return ans;
}

至于操作3.4,其实最简单了qwq。我们直接在线段树上操作就行了。

综上,我们解决了第一道树剖题。

  1 #include<cstdio>
  2 #include<algorithm>
  3 #define maxn 100090
  4 
  5 using namespace std;
  6 typedef long long ll;
  7 
  8 int n,m,root,moder,tot,cnt;
  9 int head[maxn],size[maxn],d[maxn],f[maxn],son[maxn],val[maxn];
 10 int top[maxn],id[maxn],rk[maxn];
 11 struct node{
 12     int to,next;
 13 }edge[maxn*2];
 14 struct SegmentTree{
 15     int l,r;
 16     ll w,lazy;
 17 }t[maxn*4];
 18 
 19 void add(int x,int y)
 20 {
 21     edge[++tot].to=y;
 22     edge[tot].next=head[x];
 23     head[x]=tot;
 24 }
 25 
 26 void dfs1(int u,int fa,int dep)
 27 {//第一遍dfs 预处理出f[]/d[]/son[]/size[]
 28     f[u]=fa;
 29     d[u]=dep;
 30     size[u]=1;
 31     for(int i=head[u];i;i=edge[i].next)
 32     {
 33         int v=edge[i].to;
 34         if(v==fa) continue;
 35         dfs1(v,u,dep+1);
 36         size[u]+=size[v];
 37         if(size[v]>size[son[u]])
 38             son[u]=v;
 39     }
 40 }
 41 
 42 void dfs2(int u,int t)
 43 {//第二遍dfs 预处理出id[]/rk[]/top[] 
 44     top[u]=t;
 45     id[u]=++cnt;
 46     rk[cnt]=val[u];
 47     if(!son[u]) return ;//找到了叶子
 48     //通过优先进入重儿子来保证一条重链上各节点dfs序连续 
 49     dfs2(son[u],t);
 50     for(int i=head[u];i;i=edge[i].next)
 51     {
 52         int v=edge[i].to;
 53         if(v==son[u]||v==f[u]) continue;
 54         dfs2(v,v);//在轻链上 top为本身 
 55     } 
 56 } 
 57 
 58 void update(int p)
 59 {
 60     if(t[p].l==t[p].r) return ;
 61     if(!t[p].lazy) return ;
 62     t[p*2].w+=t[p].lazy*(t[p*2].r-t[p*2].l+1);
 63     t[p*2+1].w+=t[p].lazy*(t[p*2+1].r-t[p*2+1].l+1);
 64     t[p*2].lazy+=t[p].lazy;
 65     t[p*2+1].lazy+=t[p].lazy;
 66     t[p].lazy=0;
 67 }
 68 
 69 void build(int p,int l,int r)
 70 {
 71     t[p].l=l,t[p].r=r;
 72     if(l==r)
 73     {
 74         t[p].w=rk[l];
 75         return ;
 76     }
 77     int mid=(l+r)>>1;
 78     build(p*2,l,mid);
 79     build(p*2+1,mid+1,r);
 80     (t[p].w=t[p*2].w+t[p*2+1].w)%=moder;
 81 }
 82 
 83 void change(int p,int l,int r,int x)
 84 {
 85     update(p);
 86     if(t[p].l==l&&t[p].r==r)
 87     {
 88         t[p].w+=x*(r-l+1);
 89         t[p].lazy+=x;
 90         return ;
 91     }
 92     int mid=(t[p].l+t[p].r)>>1;
 93     if(l>mid) change(p*2+1,l,r,x);
 94     else if(r<=mid) change(p*2,l,r,x);
 95     else change(p*2,l,mid,x),change(p*2+1,mid+1,r,x);
 96     (t[p].w=t[p*2].w+t[p*2+1].w)%=moder;
 97 }
 98 
 99 ll ask(int p,int l,int r)
100 {
101     update(p);
102     if(t[p].l==l&&t[p].r==r) return (t[p].w)%moder;
103     int mid=(t[p].l+t[p].r)>>1;
104     if(l>mid) return (ask(p*2+1,l,r))%moder;
105     else if(r<=mid) return (ask(p*2,l,r))%moder;
106     else return (ask(p*2,l,mid)+ask(p*2+1,mid+1,r))%moder; 
107 }
108 
109 ll treeask(int x,int y)
110 {
111     ll ans=0;
112     while(top[x]!=top[y])
113     {
114         if(d[top[x]]<d[top[y]]) swap(x,y);
115         (ans+=ask(1,id[top[x]],id[x]))%=moder;
116         x=f[top[x]];
117     }
118     if(d[x]>d[y]) swap(x,y);
119     (ans+=ask(1,id[x],id[y]))%=moder;
120     return ans;
121 }
122 
123 void treeadd(int x,int y,int w)
124 {
125     while(top[x]!=top[y])
126     {
127         if(d[top[x]]<d[top[y]]) swap(x,y);
128         change(1,id[top[x]],id[x],w);
129         x=f[top[x]];
130     }
131     if(d[x]>d[y]) swap(x,y);
132     change(1,id[x],id[y],w);
133 }
134 
135 int main()
136 {
137     scanf("%d%d%d%d",&n,&m,&root,&moder);
138     for(int i=1;i<=n;i++) scanf("%d",&val[i]);
139     for(int i=1;i<=n-1;i++)
140     {
141         int x=0,y=0;
142         scanf("%d%d",&x,&y);
143         add(x,y);add(y,x);
144     }
145     dfs1(root,0,1);
146     dfs2(root,root); 
147     build(1,1,n);
148     while(m--)
149     {
150         int opt=0;
151         scanf("%d",&opt);
152         if(opt==1)
153         {
154             int x=0,y=0,z=0;
155             scanf("%d%d%d",&x,&y,&z);
156             treeadd(x,y,z);
157         }
158         else if(opt==2)
159         {
160             int x=0,y=0;
161             scanf("%d%d",&x,&y);
162             printf("%lld\n",treeask(x,y));
163         }
164         else if(opt==3)
165         {
166             int x=0,y=0;
167             scanf("%d%d",&x,&y);
168             change(1,id[x],id[x]+size[x]-1,y);
169         }
170         else if(opt==4)
171         {
172             int x=0;
173             scanf("%d",&x);
174             printf("%lld\n",ask(1,id[x],id[x]+size[x]-1)%moder);
175         }
176     }
177     return 0;
178 }
View Code

 


 

Update:2018/10/3

早上来做了一道树链剖分...本想一次过的结果卡到九点...。

原因:单点修改的时候用的是x而不是id[x],查询最大值的时候因为忽略了还有负数,所以初始值设的是0,而应该是负无穷。

题目链接

 

Update:2018/10/6

今天又做了一道板子题==

依然改了很久==

没有什么困难的操作,有一个单点修改。

开始我是想纯用之前的单点修改不带懒标记的,后来爆零了==

改成区间修改左右端点相同的情况,再开着longlong就能A了==。

百思不得其解,难道只能化为区间修改的特殊情况了么==。

后来在金涛的指点下,发现单点修改下传一个懒标记就行了,就能A了。原因不太明...。

 

Update:2018-10-31

今日份敲模板的错误:

//size[x]=1
//chushizhi rk[p]->rk[l]
//update 一直在lazy val操作假的

转载于:https://www.cnblogs.com/nopartyfoucaodong/p/9738823.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习是一种人工智能(AI)的子领域,致力于研究如何利用数据和算法让计算机系统具备学习能力,从而能够自动地完成特定任务或者改进自身性能。机器学习的核心思想是让计算机系统通过学习数据中的模式和规律来实现目标,而不需要显式地编程。 机器学习应用非常广泛,包括但不限于以下领域: 图像识别和计算机视觉: 机器学习在图像识别、目标检测、人脸识别、图像分割等方面有着广泛的应用。例如,通过深度学习技术,可以训练神经网络来识别图像中的对象、人脸或者场景,用于智能监控、自动驾驶、医学影像分析等领域。 自然语言处理: 机器学习在自然语言处理领域有着重要的应用,包括文本分类、情感分析、机器翻译、语音识别等。例如,通过深度学习模型,可以训练神经网络来理解和生成自然语言,用于智能客服、智能助手、机器翻译等场景。 推荐系统: 推荐系统利用机器学习算法分析用户的行为和偏好,为用户推荐个性化的产品或服务。例如,电商网站可以利用机器学习算法分析用户的购买历史和浏览行为,向用户推荐感兴趣的商品。 预测和预测分析: 机器学习可以用于预测未来事件的发生概率或者趋势。例如,金融领域可以利用机器学习算法进行股票价格预测、信用评分、欺诈检测等。 医疗诊断和生物信息学: 机器学习在医疗诊断、药物研发、基因组学等领域有着重要的应用。例如,可以利用机器学习算法分析医学影像数据进行疾病诊断,或者利用机器学习算法分析基因数据进行疾病风险预测。 智能交通和物联网: 机器学习可以应用于智能交通系统、智能城市管理和物联网等领域。例如,可以利用机器学习算法分析交通数据优化交通流量,或者利用机器学习算法分析传感器数据监测设备状态。 以上仅是机器学习应用的一部分,随着机器学习技术的不断发展和应用场景的不断拓展,机器学习在各个领域都有着重要的应用价值,并且正在改变我们的生活和工作方式。
机器学习是一种人工智能(AI)的子领域,致力于研究如何利用数据和算法让计算机系统具备学习能力,从而能够自动地完成特定任务或者改进自身性能。机器学习的核心思想是让计算机系统通过学习数据中的模式和规律来实现目标,而不需要显式地编程。 机器学习应用非常广泛,包括但不限于以下领域: 图像识别和计算机视觉: 机器学习在图像识别、目标检测、人脸识别、图像分割等方面有着广泛的应用。例如,通过深度学习技术,可以训练神经网络来识别图像中的对象、人脸或者场景,用于智能监控、自动驾驶、医学影像分析等领域。 自然语言处理: 机器学习在自然语言处理领域有着重要的应用,包括文本分类、情感分析、机器翻译、语音识别等。例如,通过深度学习模型,可以训练神经网络来理解和生成自然语言,用于智能客服、智能助手、机器翻译等场景。 推荐系统: 推荐系统利用机器学习算法分析用户的行为和偏好,为用户推荐个性化的产品或服务。例如,电商网站可以利用机器学习算法分析用户的购买历史和浏览行为,向用户推荐感兴趣的商品。 预测和预测分析: 机器学习可以用于预测未来事件的发生概率或者趋势。例如,金融领域可以利用机器学习算法进行股票价格预测、信用评分、欺诈检测等。 医疗诊断和生物信息学: 机器学习在医疗诊断、药物研发、基因组学等领域有着重要的应用。例如,可以利用机器学习算法分析医学影像数据进行疾病诊断,或者利用机器学习算法分析基因数据进行疾病风险预测。 智能交通和物联网: 机器学习可以应用于智能交通系统、智能城市管理和物联网等领域。例如,可以利用机器学习算法分析交通数据优化交通流量,或者利用机器学习算法分析传感器数据监测设备状态。 以上仅是机器学习应用的一部分,随着机器学习技术的不断发展和应用场景的不断拓展,机器学习在各个领域都有着重要的应用价值,并且正在改变我们的生活和工作方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值