树链剖分入门

这几天学了一个树链剖分,觉得还不是很难,这里我试着讲一讲吧。

 

首先,我认为树链剖分是把在树上一个节点一个节点的走改为按照某种规则跳,从而降低了时间复杂度。

 

那这是什么规则呢?

首先我们得知道什么是重链,知道什么是重链就得先知道什么是重儿子,重儿子就是子树较大的儿子。然后对于一个点,我们总是往他的重儿子走,这样就构成了重链,那么剩下的就是轻链。

放张图直观些

 

然后我们同样可以对树进行dfs,只不过重儿子优先,这样我们也得到了一个dfs序,于是我们把树上问题成功转化成了线性问题。接着就可以用线段树等数据结构维护了。

 

那就拿这道板子体为例:https://www.luogu.org/problemnew/show/P3384

 

首先是两遍dfs,第一遍dfs维护子树大小size[],节点深度dep[],重儿子son[],以及一个节点的父亲节点(因为如果跳到了一条链的顶端,就要再自己走到他的父亲节点)。

 1 void dfs1(int now)
 2 {
 3     vis[now] = 1;
 4     size[now] = 1;
 5     for(int i = 0; i < (int)v[now].size(); ++i)
 6     {
 7         if(!vis[v[now][i]])
 8         {
 9             dep[v[now][i]] = dep[now] + 1;
10             fa[v[now][i]] = now;
11             dfs1(v[now][i]);
12             size[now] += size[v[now][i]];
13             if(!son[now] || size[son[now]] < size[v[now][i]]) son[now] = v[now][i];
14             //如果没有重儿子,或者当前子树大小大于重儿子的子树大小,就更新重儿子 
15         }
16     }
17 }

 

第二遍dfs是维护dfs序dfsx[],每一条链的顶端是哪一个节点。但我们还要在维护一个pos[],因为当我们将树转化成线性后,用线段树建树的时候需要添加节点,而这个节点的编号是dfs序的编号,所以需要再用一个数组记录dfs序的编号所对应的树上节点编号。

 1 int cnt = 0, dfsx[maxn], pos[maxn], top[maxn];
 2 void dfs2(int now)
 3 {
 4     //dfsx[]因为智慧更新一次,所以可以当做vis[]用 
 5     dfsx[now] = ++cnt; pos[cnt] = now;
 6     if(son[now])
 7     {
 8         top[son[now]] = top[now];
 9         dfs2(son[now]);        //优先走重儿子,保证一条链在dfs序上的编号是连续的 
10     }
11     for(int i = 0; i < (int)v[now].size(); ++i)
12     {
13         if(!dfsx[v[now][i]] && son[now] != v[now][i])    //再走不是重儿子的节点 
14         {
15             top[v[now][i]] = v[now][i];        //轻儿子所在的链只有他自己一个节点,所以顶端节点就是他自己 
16             dfs2(v[now][i]);
17         }
18     }
19 }

 

这两个预处理完事后就可以看看题了。

 

第一个询问,将树从x到y结点最短路径上所有节点的值都加上z。

首先我们要将x,y移到同一条链上,具体操作就是如果其中一个点所在链的顶端的深度更低,就将他跳到链的顶端,并更新他到顶端节点的区间。

移到同一条链上后,就更新这两个点的区间就行了

 1 void pathUpdate(int x, int y, int z)
 2 {
 3     while(top[x] != top[y])        //先把这俩搞到一条链上 
 4     {
 5         if(dep[top[x]] < dep[top[y]]) swap(x, y);    //默认让x跳 
 6         update(dfsx[top[x]], dfsx[x], 1, z);
 7         x = fa[top[x]];
 8     }
 9     if(dfsx[x] > dfsx[y]) swap(x, y);
10     update(dfsx[x], dfsx[y], 1, z);
11 }

 

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

和修改一样,先把两点移到同一条链上,然后计算跳的点在该链上的贡献

 1 ll pathQuery(int x, int y)
 2 {
 3     ll ret = 0;
 4     while(top[x] != top[y])
 5     {
 6         if(dep[top[x]] < dep[top[y]]) swap(x, y);
 7         ret += query(dfsx[top[x]], dfsx[x], 1); ret %= mod;
 8         x = fa[top[x]];
 9     }
10     if(dfsx[x] > dfsx[y]) swap(x, y);
11     ret += query(dfsx[x], dfsx[y], 1); ret %= mod;
12     return ret;
13 }

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

值得一提的是,尽管我们在维护dfs序时是重链优先遍历,但仍满足一个节点以及他的子树在dfs序上是一段长为子树大小的连续区间,自己画一画就明白了

这里和查询放一块

1 void sbtUpdate(int x, int z)
2 {
3     update(dfsx[x], dfsx[x] + size[x] - 1, 1, z);
4 }
5 ll sbtQuery(int x)
6 {
7     return query(dfsx[x], dfsx[x] + size[x] - 1, 1);
8 }

 

这样板子就写完了,是不是很简单?

然后最重要的一件事是别忘了取模,而且每一个运算后都要取,否则你就可能70分代码debug一小时……

  1 #include<cstdio>
  2 #include<iostream>
  3 #include<cstring>
  4 #include<cmath>
  5 #include<algorithm>
  6 #include<vector>
  7 #include<cctype>
  8 using namespace std;
  9 #define enter printf("\n")
 10 #define space printf(" ")
 11 typedef long long ll;
 12 const int INF = 0x3f3f3f3f;
 13 const int maxn = 1e5 + 5;
 14 inline ll read()
 15 {
 16     ll ans = 0;
 17     char ch = getchar(), last = ' ';
 18     while(!isdigit(ch)) {last = ch; ch = getchar();}
 19     while(isdigit(ch))
 20     {
 21         ans = ans * 10 + ch - '0'; ch = getchar();    
 22     }
 23     if(last == '-') ans = -ans;
 24     return ans;
 25 }
 26 inline void write(ll x)
 27 {
 28     if(x < 0) x = -x, putchar('-');
 29     if(x >= 10) write(x / 10);
 30     putchar('0' + x % 10);
 31 }
 32 
 33 int n, m, s, mod;
 34 int a[maxn];
 35 vector<int> v[maxn];
 36 
 37 bool vis[maxn];
 38 int fa[maxn], son[maxn], size[maxn], dep[maxn];
 39 void dfs1(int now)
 40 {
 41     vis[now] = 1;
 42     size[now] = 1;
 43     for(int i = 0; i < (int)v[now].size(); ++i)
 44     {
 45         if(!vis[v[now][i]])
 46         {
 47             dep[v[now][i]] = dep[now] + 1;
 48             fa[v[now][i]] = now;
 49             dfs1(v[now][i]);
 50             size[now] += size[v[now][i]];
 51             if(!son[now] || size[son[now]] < size[v[now][i]]) son[now] = v[now][i];
 52             //如果没有重儿子,或者当前子树大小大于重儿子的子树大小,就更新重儿子 
 53         }
 54     }
 55 }
 56 //第二遍dfs是维护dfs序dfsx[],每一条链的顶端是哪一个节点
 57 
 58 int cnt = 0, dfsx[maxn], pos[maxn], top[maxn];
 59 void dfs2(int now)
 60 {
 61     //dfsx[]因为智慧更新一次,所以可以当做vis[]用 
 62     dfsx[now] = ++cnt; pos[cnt] = now;
 63     if(son[now])
 64     {
 65         top[son[now]] = top[now];
 66         dfs2(son[now]);        //优先走重儿子,保证一条链在dfs序上的编号是连续的 
 67     }
 68     for(int i = 0; i < (int)v[now].size(); ++i)
 69     {
 70         if(!dfsx[v[now][i]] && son[now] != v[now][i])    //再走不是重儿子的节点 
 71         {
 72             top[v[now][i]] = v[now][i];        //轻儿子所在的链只有他自己一个节点,所以顶端节点就是他自己 
 73             dfs2(v[now][i]);
 74         }
 75     }
 76 }
 77 
 78 int l[maxn << 2], r[maxn << 2];
 79 ll sum[maxn << 2], lazy[maxn << 2];
 80 void build(int L, int R, int now)
 81 {
 82     l[now] = L; r[now] = R;
 83     if(L == R) {sum[now] = a[pos[L]]; return;}
 84     int mid = (L + R) >> 1;
 85     build(L, mid, now << 1);
 86     build(mid + 1, R, now << 1 | 1);
 87     sum[now] = (sum[now << 1] + sum[now << 1 | 1]) % mod; 
 88 }
 89 void pushdown(int now)
 90 {
 91     if(lazy[now])
 92     {
 93         lazy[now << 1] += lazy[now]; lazy[now << 1] %= mod;
 94         lazy[now << 1 | 1] += lazy[now]; lazy[now << 1 | 1] %= mod;
 95         sum[now << 1] += (ll)(r[now << 1] - l[now << 1] + 1) * lazy[now]; sum[now << 1] %= mod;
 96         sum[now << 1 | 1] += (ll)(r[now << 1 | 1] - l[now << 1 | 1] + 1) * lazy[now]; sum[now << 1 | 1] %= mod;
 97         lazy[now] = 0;
 98     }
 99 }
100 void update(int L, int R, int now, int d)
101 {
102     if(L == l[now] && R == r[now])
103     {
104         sum[now] += (ll)(r[now] - l[now] + 1) * d; sum[now] %= mod;
105         lazy[now] += d; lazy[now] %= mod;
106         return;
107     }
108     pushdown(now);
109     int mid = (l[now] + r[now]) >> 1;
110     if(R <= mid) update(L, R, now << 1, d);
111     else if(L > mid) update(L, R, now << 1 | 1, d);
112     else {update(L, mid, now << 1, d); update(mid + 1, R, now << 1 | 1, d);}
113     sum[now] = sum[now << 1] + sum[now << 1 | 1];
114 }
115 ll query(int L, int R, int now)
116 {
117     if(L == l[now] && R == r[now]) return sum[now];
118     pushdown(now);
119     int mid = (l[now] + r[now]) >> 1;
120     if(R <= mid) return query(L, R, now << 1);
121     else if(L > mid) return query(L, R, now << 1 | 1);
122     else return (query(L, mid, now << 1) + query(mid + 1, R, now << 1 | 1)) % mod;
123 }
124 
125 void pathUpdate(int x, int y, int z)
126 {
127     while(top[x] != top[y])        //先把这俩搞到一条链上 
128     {
129         if(dep[top[x]] < dep[top[y]]) swap(x, y);    //默认让x跳 
130         update(dfsx[top[x]], dfsx[x], 1, z);
131         x = fa[top[x]];
132     }
133     if(dfsx[x] > dfsx[y]) swap(x, y);
134     update(dfsx[x], dfsx[y], 1, z);
135 }
136 ll pathQuery(int x, int y)
137 {
138     ll ret = 0;
139     while(top[x] != top[y])
140     {
141         if(dep[top[x]] < dep[top[y]]) swap(x, y);
142         ret += query(dfsx[top[x]], dfsx[x], 1); ret %= mod;
143         x = fa[top[x]];
144     }
145     if(dfsx[x] > dfsx[y]) swap(x, y);
146     ret += query(dfsx[x], dfsx[y], 1); ret %= mod;
147     return ret;
148 }
149 
150 void sbtUpdate(int x, int z)
151 {
152     update(dfsx[x], dfsx[x] + size[x] - 1, 1, z);
153 }
154 ll sbtQuery(int x)
155 {
156     return query(dfsx[x], dfsx[x] + size[x] - 1, 1);
157 }
158 
159 int main()
160 {
161     n = read(); m = read(); s = read(); mod = read();
162     for(int i = 1; i <= n; ++i) a[i] = read();
163     for(int i = 1 ; i < n; ++i)
164     {
165         int a = read(), b = read();
166         v[a].push_back(b); v[b].push_back(a);
167     }
168     dfs1(s); 
169     top[s] = s; dfs2(s);    
170     build(1, n, 1);
171     for(int i = 1; i <= m ; ++i)
172     {
173         int d = read();
174         if(d == 1) 
175         {
176             int x = read(), y = read(), z = read();
177             pathUpdate(x, y, z);
178         }
179         else if(d == 2)
180         {
181             int x = read(), y = read();
182             write(pathQuery(x, y)); enter;
183         }
184         else if(d == 3)
185         {
186             int x = read(), z = read();
187             sbtUpdate(x, z);
188         }
189         else
190         {
191             int x = read();
192             write(sbtQuery(x)); enter;
193         }
194     }
195     return 0;
196 }

 

转载于:https://www.cnblogs.com/mrclr/p/9375402.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/451877.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学成在线--23.课程图片管理(上传图片)

文章目录一. 需求分析1). 需求分析2). 图片上传流程二. 创建文件系统服务工程1). 工程目录结构2). 项目依赖pom.xml3). 配置文件application.yml三. 后端开发1. 模型类1). 模型类2). Collection2. Api接口3. Dao4. Service5. Controller6. 测试四. 前端开发1. 需求2. 页面1). T…

13个超棒的代码资源网站推荐

很多开发者都有过网站开发的经历&#xff0c;大家使用CSS、HTML以及JavaScript等技术来完成这一工作。但想必大家也知道&#xff0c;网站开发是一个很耗费时间的工作。你可能需要花费大量的时间在一些网站上寻找解决问题的代码段。这的确很耗费时间&#xff0c;但却几乎又是不可…

Jquery Datatable的使用样例(ssm+bootstrsp框架下)服务器端分页

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 效果&#xff1a; 我这个表格数据 比较少没有第2页 有多例多页的效果&#xff08;带滚动条和翻页&#xff09;&#xff1a; 1. jsp页面…

学成在线--24.课程图片管理(保存课程图片)

文章目录一. 需求分析二. 服务端开发1. 模型类2. API3. Dao4. Service5. Controller三. 前端开发1. API2. 页面1). 添加上传成功的钩子 :on-success"handleSuccess"2). 在钩子方法 中保存课程图片信息一. 需求分析 图片上传到文件系统后&#xff0c;其它子系统如果想…

从任意网页上摘取酷炫Jquery效果为自己使用的方法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 用的chrome 浏览器 2. 随意百度一个漂亮的jquery效果 比如我找到一个可以旋转的多面体效果 3. 再F12选 Resources到如下界面&…

shell基础05 处理用户输入

1. 命令行参数------类似javac 参数1 参数2 类似Java中编译的javac parm1....。在shell中&#xff0c;参数与参数之间用空格隔开。采用位置参数来识别对应的参数值&#xff1a;$0是程序名&#xff0c;$1是第一个参数&#xff0c;以此类推&#xff0c;知道第9个参数$9。对于大…

OpenCV 2.4.0 正式版发布,开源计算机视觉库

OpenCV 于近日发布了 2.4.0 正式版。 OpenCV是一个基于BSD许可证授权发行的跨平台开源计算机视觉库&#xff0c;可以运行在Linux、Windows和Mac OS操作系统上。作为一款简洁而且高效的视觉库&#xff0c;OpenCV由一系列 C 函数和少量 C 类构成&#xff0c;同时提供了Python、Ru…

最小编辑代价-golang

题目&#xff1a; 给定两个字符串str1和str2&#xff0c;在给定三个整数ic,dc和rc,分别代表插入、删除和替换一个 字符&#xff0c;返回将str1编辑成str2的最小代价。 解题方法&#xff1a; 动态规划。首先生成大小为(M1)X(N1)的矩阵dp。 假设str1"avb12cd3", str2&q…

You can't specify target table 'TS_AUTH_ADMIN' for update in FROM clause记录

&#xff11;. 报错&#xff1a;You cant specify target table TS_AUTH_ADMIN for update in FROM clause&#xff0c; 百度查到说是&#xff0c;不能在同一语句中先select出同一表中的某些值,再update这个表 。 我原本的sql是&#xff1a;&#xff08;删除角色的时候&#…

study of javaserver faces lifecycle

JavaServer Faces应用程序的生命周期在客户端为页面发出HTTP请求时开始&#xff0c;并在服务器响应该页面并转换为HTML时结束。 通常将JSF的生命周期分为两个阶段&#xff1a; #执行阶段 #渲染阶段 1.执行阶段 JavaServer Faces应用程序生命周期执行阶段包含以下子阶段&#xf…

sql语句update中多个case/when的写法

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 又如&#xff1a; update xxxx_xxxx set xxx_typeCASE WHEN xxx_type 0 THENYXLX-0WHEN xxx_type 1 THENYXLX-1WHEN xxx_type 2 THE…

警惕开源代码库中的安全隐患

最近的一项研究发现&#xff0c; 在调查的31个流行库&#xff08;框架&#xff09;的1261个版本中&#xff0c;超过三分之一存在已知的安全漏洞&#xff0c;大约四分之一的下载文件已经被污染。 该项研究由Aspect Security和Sonatype发起。Aspect Security是一家评估软件安全漏…

线程间的协作(3)——管道输入/输出流

2019独角兽企业重金招聘Python工程师标准>>> 1.管道输入/输出流类 分为两类&#xff0c;字节流管道类&#xff08;PipedInputStream/PipedOutputStream&#xff09;和字符流管道类&#xff08;PipedReader/ PipedWriter&#xff09;。这两个IO流实现了可以在不同的任…

windows简易版本 Redis 使用 demo样例(ssm框架下)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. 在网上下载 windows 版本 的Redis 。下载了直接解压出来 &#xff1a; 2. 双击 redis-server.exe 启动服务&#xff08;如下图&#…

程序员的半衰期只有15年?

曾在Google工作负责过技术工作的科技编辑 Matt Heusser总结了他在Google的生活经历&#xff0c;得出结论&#xff1a; 作为程序员&#xff0c;你只有15年时间。Matt 写道当我在Google工作时&#xff0c;发现Google大部分人都是20出头的年轻人&#xff0c;他们经历的很多事情都是…

EasyNVR、EasyDSS二次开发之:RTMP、HLS流在web页面进行无插件播放示例Demo代码

不管是基于EasyNVR还是EasyDSS&#xff0c;都是支持无插件直播&#xff0c;这也是未来视频直播的一个趋势。对于传统的浏览器插件播放谁用谁知道&#xff1b; 以上是软件自带播放展示 背景需求 对于EasyNVR和EasyDSS的使用方式大概分为两大类&#xff0c;一类是直接将软件作为视…

11--移除重复节点

编写代码&#xff0c;移除未排序链表中的重复节点。保留最开始出现的节点。 示例1: 输入&#xff1a;[1, 2, 3, 3, 2, 1] 输出&#xff1a;[1, 2, 3] 示例2: 输入&#xff1a;[1, 1, 1, 1, 2] 输出&#xff1a;[1, 2]

信息图:程序员/开发人员实际在用哪些工具

BestVendor.com的工作人员在全球范围内采访了500名重要开发人员&#xff0c;在调查询问他们实际使用的工具后&#xff0c;制作了一张信息图&#xff0c;如下。 这张信息图覆盖10个方面的工具&#xff1a;Bug 追踪、数据库、开发框架、集成开发环境&#xff08;IDE&#xff09;、…