线段树&数链剖分
傻逼线段树,傻逼数剖
线段树
定义:
线段树的思想和分治思想很相像。
线段树的每一个节点都储存着一段区间[L…R]的信息,其中叶子节点L=R。它的大致思想是:将一段大区间平均地划分成2个小区间,每一个小区间都再平均分成2个更小区间……以此类推,直到每一个区间的L等于R(这样这个区间仅包含一个节点的信息,无法被划分)。通过对这些区间进行修改、查询,来实现对大区间的修改、查询。
这样一来,每一次修改、查询的时间复杂度都只为O(log2n)。
- void build(int l,int r,int i){//建树,为当前左边界,r为右边界,i为编号
- tree[i].l=l;//更新边界值
- tree[i].r=r;
- if(l==r){//如果是最底层的节点,sum就是本身
- tree[i].sum=input[l];
- return ;
- }
- int mid=(l+r)/2;
- build(l,mid,2*i);//左边部分建树
- build(mid+1,r,2*i+1);//右边部分建树
- tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;//递归返回时更新sum值
- }
区间修改:
- void add(int i,int L,int R,int k){//区间修改 ;
- if(tree[i].l>=L&&tree[i].r<=R){//被完全包含
- tree[i].sum=tree[i].sum+(tree[i].r-tree[i].l+1)*k; //修改区间
- tree[i].lazy+=k;//更新延迟标记
- return ;
- }
- push_down(i);//没有完全包含的话就先下传懒标记
- int mid=(tree[i].l+tree[i].r)/2;
- if(L<=mid) add(2*i,L,R,k);//左边有重合走左边
- if(mid<R) add(2*i+1,L,R,k);//右边有重合走右边
- //这里一定不能写mid<=R,不然会死循环
- tree[i].sum=tree[2*i].sum+tree[2*i+1].sum//更新sum值
- }
区间查询:
- int ask(int i,int L,int R){//区间查询
- if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;//完全包含,道理同区间修改
- push_down(i);//没有完全包含就下传延迟标记
- int ans=0;
- int mid=(tree[i].l+tree[i].r)/2;
- if(mid>=L) ans=(ans+ask(2*i,L,R));
- if(mid<R) ans=(ans+ask(2*i+1,L,R));//记录答案
- return ans;
- }
下传延迟标记(push_down操作):
- void push_down(int i){//延迟标记下移
- if(tree[i].lazy){
- tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
- tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
- tree[2*i].lazy+=tree[i].lazy;
- tree[2*i+1].lazy+=tree[i].lazy;
- tree[i].lazy=0;
- }
- }
延迟标记的作用:
有些时候修改了也不一定会去查询,于是就打上延迟标记,需要的时候再下传。
板题:https://www.luogu.com.cn/problem/P3372
Code:
- #include<bits/stdc++.h>
- using namespace std;
- const int N=1e5+5;
- int n,m;
- int type,x,y,k;
- long long input[N];
- struct node {
- int l;
- int r;
- long long sum;
- long long add;
- } tree[N*4];
- void spread(int i) {
- if(tree[i].add) {
- tree[2*i].sum+=tree[i].add*(tree[2*i].r-tree[2*i].l+1);
- tree[2*i+1].sum+=tree[i].add*(tree[2*i+1].r-tree[2*i+1].l+1);
- tree[2*i].add+=tree[i].add;
- tree[2*i+1].add+=tree[i].add;
- tree[i].add=0;
- }
- }
- void build(int i,int l,int r) {
- tree[i].l=l;
- tree[i].r=r;
- if(l==r) {
- tree[i].sum=input[l];
- return ;
- }
- int mid=(l+r)/2;
- build(2*i,l,mid);
- build(2*i+1,mid+1,r);
- tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
- }
- void change(int i,int l,int r,int k) {
- if(l<=tree[i].l&&r>=tree[i].r) {
- tree[i].sum+=k*(tree[i].r-tree[i].l+1);
- tree[i].add+=k;
- return ;
- }
- spread(i);
- int mid=(tree[i].l+tree[i].r)/2;
- if(l<=mid)change(2*i,l,r,k);
- if(r>mid)change(2*i+1,l,r,k);
- tree[i].sum=tree[2*i].sum+tree[2*i+1].sum;
- }
- long long check(int i,int l,int r) {
- if(l<=tree[i].l&&r>=tree[i].r) {
- return tree[i].sum;
- }
- spread(i);
- long long flag=0;
- int mid=(tree[i].l+tree[i].r)/2;
- if(l<=mid) flag+=check(2*i,l,r);
- if(r>mid) flag+=check(2*i+1,l,r);
- return flag;
- }
- int main() {
- cin>>n>>m;
- for(int i=1; i<=n; i++) {
- cin>>input[i];
- }
- build(1,1,n);
- for(int i=1; i<=m; i++) {
- cin>>type;
- if(type==1) {
- cin>>x>>y>>k;
- change(1,x,y,k);
- }
- if(type==2) {
- cin>>x>>y;
- cout<<check(1,x,y)<<endl;
- }
- }
- return 0;
- }
好的接下来来到——
树剖
定义:我们以某种规则将一棵树剖分成若干条竖直方向上的链,每次维护时可以一次跳一条链、并借助一些强大的线性数据结构来维护(通常链的数量很少),这样就大大优化了时间复杂度,足以解决很多线性结构搬到树上的题目。
变量:
- //son[N] 重儿子的编号,若没有重儿子则编号为-1
- //size[N] 子树的大小
- //f[N] 父亲节点的编号
- //d[N] 结点的深度
- //top[N] 所在链的链端
- //id[N] 经过重链剖分后的新编号
- //rk[N] 有rk[id[i]]=i
将树剖分成链的过程中,我们一共要进行两次dfs:
- void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
- f[x]=fa;//更新父结点
- d[x]=depth;//更新深度
- size[x]=1;//子树大小初始化:根节点本身
- for(int i=head[x];i;i=nex[i]){
- int y=ver[i];
- if(y==fa) continue ;
- dfs1(y,x,depth+1);
- if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
- size[x]+=size[y];//更新子树大小
- }
- return ;
- } //dfs1更新f[N],d[N],size[N],son[N]
- void dfs2(int u,int t){//u为当前结点,t为链段
- top[u]=t;
- id[u]=++cnt;
- rk[cnt]=u;//新的编号
- if(!son[u]) return ;
- dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
- for(int i=head[u];i;i=nex[i]){
- int y=ver[i];
- if(y==son[u]||y==f[u]) continue;
- dfs2(y,y);//再建一条链
- }
- }//dfs2更新top[N],id[N],rk[N]
至此我们剖分的过程就已经完成了,现在让我们看看在题目中树链剖分有什么用:
题目:https://www.luogu.com.cn/problem/P3384
题目要求我们进行如下操作:
操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。(区间修改)
操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。(区间查询)
操作 3: 格式: 3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z。(区间修改)
操作 4: 格式: 4 x 表示求以 x 为根节点的子树内所有节点值之和。(区间查询)
操作一:
- void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
- if(d[x]<d[y]) swap(x,y);
- while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
- if(d[top[x]]<d[top[y]]) swap(x,y);//规范
- add(1,id[top[x]],id[x],k);
- x=f[top[x]];
- }
- if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
- add(1,id[x],id[y],k);
- }
其实原理就和倍增求LCA差不多。
操作二:
- void func2(int x,int y){
- int ans=0;
- while(top[x]!=top[y]){
- if(d[top[x]]<d[top[y]]) swap(x,y);
- ans=(ans+ask(1,id[top[x]],id[x]))%mod;
- x=f[top[x]];
- }
- if(d[x]>d[y]) swap(x,y);//道理同上
- ans=(ans+ask(1,id[x],id[y]));
- cout<<ans%mod<<endl;
- }
原理和func1差不多啦~
操作三&&操作四:
这里的处理比较巧妙。
在树剖中,一条链的编号是连续的,因此一棵子树的编号也是连续的。
所以直接用线段树的区间修改和区间查询操作就行了。
Code:
- #include<bits/stdc++.h>
- #define int long long
- using namespace std;
- const int N=1e6+5;
- int n,m,root,mod;
- int idx=0;
- struct node{
- int l;
- int r;
- int sum;
- int lazy;
- }tree[4*N];
- int head[N],ver[2*N],nex[2*N],son[N],size[N],f[N],d[N],top[N],id[N],rk[N];
- int input[N];
- int cnt=0;
- void add_e(int x,int y){
- ver[++idx]=y;
- nex[idx]=head[x];
- head[x]=idx;
- }
- void build(int l,int r,int i){//常规建树
- tree[i].l=l;
- tree[i].r=r;
- if(l==r){
- tree[i].sum=input[rk[l]]%mod;
- return ;
- }
- int mid=(l+r)/2;
- build(l,mid,2*i);
- build(mid+1,r,2*i+1);
- tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
- }
- void push_down(int i){//延迟标记下移
- if(tree[i].lazy){
- tree[2*i].sum=(tree[2*i].sum+(tree[2*i].r-tree[2*i].l+1)* tree[i].lazy%mod)%mod;
- tree[2*i+1].sum=(tree[2*i+1].sum+(tree[2*i+1].r-tree[2*i+1].l+1)* tree[i].lazy%mod)%mod;
- tree[2*i].lazy+=tree[i].lazy;
- tree[2*i+1].lazy+=tree[i].lazy;
- tree[i].lazy=0;
- }
- }
- int ask(int i,int L,int R){//区间查询
- if(tree[i].l>=L&&tree[i].r<=R) return tree[i].sum;
- push_down(i);
- int ans=0;
- int mid=(tree[i].l+tree[i].r)/2;
- if(mid>=L) ans=(ans+ask(2*i,L,R))%mod;
- if(mid<R) ans=(ans+ask(2*i+1,L,R))%mod;
- return ans%mod;
- }
- void add(int i,int L,int R,int k){//区间修改 ;
- if(tree[i].l>=L&&tree[i].r<=R){
- tree[i].sum=(tree[i].sum+(tree[i].r-tree[i].l+1)*k%mod)%mod;
- tree[i].lazy+=k;
- return ;
- }
- push_down(i);
- int mid=(tree[i].l+tree[i].r)/2;
- if(L<=mid) add(2*i,L,R,k);
- if(mid<R) add(2*i+1,L,R,k);
- tree[i].sum=(tree[2*i].sum+tree[2*i+1].sum)%mod;
- }
- void dfs1(int x,int fa,int depth){//x:当前结点 fa:父结点 depth:当前结点深度
- f[x]=fa;//更新父结点
- d[x]=depth;//更新深度
- size[x]=1;//子树大小初始化:根节点本身
- for(int i=head[x];i;i=nex[i]){
- int y=ver[i];
- if(y==fa) continue ;
- dfs1(y,x,depth+1);
- if(size[y]>size[son[x]]) son[x]=y;//更新重儿子
- size[x]+=size[y];//更新子树大小
- }
- return ;
- }
- void dfs2(int u,int t){//u为当前结点,t为链段
- top[u]=t;
- id[u]=++cnt;
- rk[cnt]=u;//新的编号
- if(!son[u]) return ;
- dfs2(son[u],t);//优先遍历重儿子,使一条链上编号连续
- for(int i=head[u];i;i=nex[i]){
- int y=ver[i];
- if(y==son[u]||y==f[u]) continue;
- dfs2(y,y);//再建一条链
- }
- }
- void func1(int x,int y,int k){//将树从 x到 y结点最短路径上所有节点的值都加上k
- if(d[x]<d[y]) swap(x,y);
- while(top[x]!=top[y]){//循环,直到这两个点处于同一条链
- if(d[top[x]]<d[top[y]]) swap(x,y);//规范
- add(1,id[top[x]],id[x],k);
- x=f[top[x]];
- }
- if(d[x]>d[y]) swap(x,y);//深度较浅的一定是序号较小的
- add(1,id[x],id[y],k);
- }
- void func2(int x,int y){
- int ans=0;
- while(top[x]!=top[y]){
- if(d[top[x]]<d[top[y]]) swap(x,y);
- ans=(ans+ask(1,id[top[x]],id[x]))%mod;
- x=f[top[x]];
- }
- if(d[x]>d[y]) swap(x,y);//道理同上
- ans=(ans+ask(1,id[x],id[y]));
- cout<<ans%mod<<endl;
- }
- signed main(){
- cin>>n>>m>>root>>mod;
- for(int i=1;i<=n;i++){
- cin>>input[i];//输入节点初始值
- }
- for(int i=1;i<=n-1;i++){
- int x,y;
- cin>>x>>y;
- add_e(x,y);
- add_e(y,x);//建图
- }
- dfs1(root,0,1);//第一次dfs求son,depth,f,size
- dfs2(root,root);//第二次dfs求id,rk,将树拆成链表
- build(1,n,1);//建树
- for(int i=1;i<=m;i++){
- int type;
- cin>>type;
- if(type==1){
- int x,y,z;
- cin>>x>>y>>z;
- func1(x,y,z);
- }
- if(type==2){
- int x,y;
- cin>>x>>y;
- func2(x,y);
- }
- if(type==3){
- int x,z;
- cin>>x>>z;
- add(1,id[x],id[x]+size[x]-1,z);
- }
- if(type==4){
- int x;
- cin>>x;
- cout<<ask(1,id[x],id[x]+size[x]-1)%mod<<endl;
- }
- }
- return 0;
- }
完结撒花*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
线段树&数链剖分的更多相关文章
- G - Game HDU - 5242 (数链剖分)
题目链接: G - Game HDU - 5242 题目大意:首先是T组测试样例,给出一颗以1节点为根的树,每个节点有各自的价值,有m次从根节点出发向下走到叶子节点的机会,每次会得到所有经过节点的权值 ...
- 数链剖分(树的统计Count )
题目链接:https://cn.vjudge.net/contest/279350#problem/C 具体思路:单点更新,区间查询,查询的时候有两种操作,查询区间最大值和区间和. 注意点:在查询的时 ...
- 数链剖分(Aragorn's Story )
题目链接:https://vjudge.net/contest/279350#problem/A 题目大意:n个点,m条边,然后q次询问,因为在树上,两个点能确定一条直线,我们可以对这条直线上的所有值 ...
- POJ 2352 Stars 线段树 数星星
转载自 http://www.cnblogs.com/fenshen371/archive/2013/07/25/3214927.html 题意:已知n个星星的坐标.每个星星都有一个等级,数值等于坐标 ...
- 数链剖分(Tree)
题目链接:https://cn.vjudge.net/contest/279350#problem/D 题目大意:操作,单点查询,区间取反,询问区间最大值. AC代码: #include<ios ...
- 数链剖分(Housewife Wind )
题目链接:https://vjudge.net/contest/279350#problem/B 题目大意:给你n,q,s.n指的是有n个点,q代表有q次询问,s代表的是起点.然后接下来会有n-1条 ...
- UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html 题目传送门 - UOJ#30 题意 uoj写的很简洁.清晰,这里就不抄一遍了. 题解 首先建 ...
- POJ 2763 Housewife Wind 【树链剖分】+【线段树】
<题目链接> 题目大意: 给定一棵无向树,这棵树的有边权,这棵树的边的序号完全由输入边的序号决定.给你一个人的起点,进行两次操作: 一:该人从起点走到指定点,问你这段路径的边权总和是多少. ...
- BZOJ1758[Wc2010]重建计划——分数规划+长链剖分+线段树+二分答案+树形DP
题目描述 输入 第一行包含一个正整数N,表示X国的城市个数. 第二行包含两个正整数L和U,表示政策要求的第一期重建方案中修建道路数的上下限 接下来的N-1行描述重建小组的原有方案,每行三个正整数Ai, ...
随机推荐
- 从微信小程序到鸿蒙js开发【05】——tabs组件&每日新闻
目录: 1.tabs, tab-bar, tab-content 2.tabs的事件处理 3.tabs实现的每日新闻 1.tabs, tab-bar, tab-content 上章说到,鸿蒙的list ...
- SOLID:面向对象设计的五个基本原则
在程序设计领域,SOLID 是由罗伯特·C·马丁在 21 世纪早期引入的记忆术首字母缩略字,指代了面向对象编程和面向对象设计的五个基本原则.当这些原则被一起应用时,它们使得一个程序员开发一个容易进行软 ...
- springBoot之 spring-boot-starter-parent 引入详解
springBoot中引入 <parent> <groupId>org.springframework.boot</groupId> <artifactId& ...
- leetcode常见问题
开学了 开始每日刷leetcode了 ,开一个新分类记录做题过程和心得. 1.出现本地调试无问题但提交后报错时,很有可能是全局变量导致的,解决办法 (1).尽量写成局部变量,函数尽量传参进入. (2 ...
- 1006 How many?
Time Limit: 3000MS Memory Limit: 65536K Total Submissions: 36 Accepted: 2 Description 有一天,小Q给了小J ...
- 牛客小白月赛30 B.最好的宝石 (线段树)
题意:RT. 题解:很明显的线段树维护区间最大值操作,但是我们同时还要维护最大值的个数,我们在build或者modify操作完子树然后push_up的时候,我们先从两个儿子取max更新父节点的最大值, ...
- Codeforces Round #643 (Div. 2) E. Restorer Distance (贪心,三分)
题意:给你\(n\)个数,每次可以使某个数++,--,或使某个数--另一个++,分别消耗\(a,r,m\).求使所有数相同最少的消耗. 题解:因为答案不是单调的,所以不能二分,但不难发现,答案只有一个 ...
- Codeforces Round #666 (Div. 2) C. Multiples of Length (贪心)
题意:给你一个由\(0,1,?\)组成的字符串,你可以将\(?\)任意改成\(0\)或\(1\),问你操作后能否使得该字符串的任意长度为\(k\)的区间中的\(0\)和$1的个数相等. 题解:我们首先 ...
- 开源RPA软件试用
优点 缺点 其它 Robot Framework 可视化界面 运行环境搭建复杂,依赖较多 操作复杂 倾向于自动化测试 TagUI 浏览器支持好 官方文档详细 命令行操作 非浏览器程序支持一般 ...
- [Golang]-4 错误处理、Panic、Defer
目录 错误和异常 案例 Panic Defer 使用 defer+recover 来处理错误 参考文章: Go 语言使用一个独立的·明确的返回值来传递错误信息的.这与使用异常的 Java 和 Ruby ...