作者作为一个蒟蒻,也是最近才自学了线段树,不对的地方欢迎大佬们评论,但是不要喷谢谢

好啦,我们就开始说说线段树吧

线段树是个支持区间操作和查询的东东,平时的话还是蛮实用的

下面以最基本的区间加以及查询区间和为例

线段树顾名思义就是棵树嘛,叶子节点是每个基本点,它们所对应的父亲就是它们的和,具体如下图

但是对于这样的线段树来说,操作所需的时间是远达不到我们的要求的(会被t),因为我们会进行一些不必要的操作,就像如果没有查询到某个点,那么就没有必要去修改这个点的值,为此,我们会引入一个懒标记,记录每个基本点需要被加上的值(称为add),那么树上任意一个点需要增加的值=该点对应的区间长度*add

那么总的来说,线段树的基本操作我个人认为可以分成3个,建树、修改和查询,当然如果继续细分也是口以(可以)的,就比如说还可以分出 区间和的向上传递(父亲节点等于子节点的和)和懒标记的向下传递(子节点的懒标记=原来的懒标记+父节点的懒标记)

所以接下来我们就来看看建树、修改和查询这3部分的具体代码吧(深呼吸)

首先是建树(build)

#define ls 2*rt,l,(l+r)/2                //left son
#define rs 2*rt+1,(l+r)/2+1,r //right son
#define ll long long
void build(ll rt,ll l,ll r)//rt是当前点,l和r代表l到r区间的和
{
if(r==l)
//也就是说,我们找到了一个叶子节点,自己到自己的和 就是自己嘛
{
scanf("%lld",&su[rt]);//那我们就输入这个节点的值
}
else//否则就去看看当前点的左右儿子
{
build(ls);//看左儿子
build(rs);//看右儿子
//当rt的左右儿子都准备好了,我们就可以求出rt的值了
su[rt]=su[*rt]+su[*rt+];
}
return;
} //一层一层的求,我们就可以建好一个初步的树啦

然后是修改(change)

#define ls 2*rt,l,(l+r)/2          //左右儿子,和之前一样
#define rs 2*rt+1,(l+r)/2+1,r
#define ll long long
void change(ll rt,ll l,ll r,ll L,ll R,ll add)
//当前点,当前区间的左右端点,需要修改的区间的左右端点,需要给每个基本点加上的值
{
if(l>=L&&r<=R)//如果说当前区间是需要修改区间的子集
{
su[rt]+=add*(r-l+);
//那么就修改当前点,注意乘上当前区间长度
o[rt]+=add;
//记得修改懒标记
return;//别忘了返回!
}
if(o[rt]!=)
//如果说我们恰好经过了一个被打上懒标记的点,那不如就顺手把它的懒标记下传好了
{
//修改左右儿子的值
su[rt*]+=o[rt]*((r+l)/-l+);// (r+l)/2是区间中点
su[rt*+]+=o[rt]*(r-(r+l)/);//实际应乘以(r-((r+l)/2+1)+1)但+1-1抵消了
o[rt*]+=o[rt];
o[rt*+]+=o[rt];
//下传懒标记注意是加上父节点的懒标记不是等于
o[rt]=;//清除懒标记
}
if(L<=(l+r)/)
//二分思想,如果需要修改的区间左端点在当前区间中点的左边,即当前区间中点左侧有需要修改的点的话
{
change(ls,L,R,add);//那就去修改啊
}
if(R>(l+r)/)//同理
{
change(rs,L,R,add);
}
su[rt]=su[*rt]+su[*rt+];//橘氏春秋有云(什么鬼):有下就有上,改完记得上传
return;
}

呼啊,已经完成2/3了,坚持就是胜利!↖(^ω^)↗

查询(find)

void find(ll rt,ll l,ll r,ll L,ll R)
//当前点,当前区间左右端点,需要查询的区间左右端点
{
if(l>=L&&r<=R)//如果当前区间是查询区间的子集
{
ans[c]+=su[rt];//答案就加上当前点的值
}
else//不然就找找它应该在那个区间里面
{
if(o[rt]!=)//顺便下传rt的懒标记
{
su[rt*]+=o[rt]*((r+l)/-l+);
su[rt*+]+=o[rt]*(r-(r+l)/);
o[rt*]+=o[rt];
o[rt*+]+=o[rt];
o[rt]=;
}
if(L<=(l+r)/)//二分思想,如果左边有点
{
find(ls,L,R);//那就找找左边
}
if(R>(l+r)/)//如果右边有点
{
find(rs,L,R);//那就找找右边
}
su[rt]=su[*rt]+su[*rt+];//还是那句老话,橘氏春秋有云:有下就有上
}
return;//看到return我就开心↖(^ω^)↗
}

哇吼,结束了才怪,接下来是总代码!

//线段树要写成lazy[i]+=lazy[祖先]的形式
//温馨提示,炒鸡重要,我这个蒟蒻就被坑了嘤嘤嘤
#include<iostream>
#include<cstdio>
#define ls 2*rt,l,(l+r)/2
#define rs 2*rt+1,(l+r)/2+1,r
#define ll long long
using namespace std;
int n,m,a,c;
ll su[],x,y,k,ans[],o[];//数组开4倍
void build(ll rt,ll l,ll r)
{
if(r==l)
{
scanf("%lld",&su[rt]);
}
else
{
build(ls);
build(rs);
su[rt]=su[*rt]+su[*rt+];
}
return;
}
void change(ll rt,ll l,ll r,ll L,ll R,ll add)
{
if(l>=L&&r<=R)
{
su[rt]+=add*(r-l+);
o[rt]+=add;
return;
}
if(o[rt]!=)
{
su[rt*]+=o[rt]*((r+l)/-l+);
su[rt*+]+=o[rt]*(r-(r+l)/);
o[rt*]+=o[rt];
o[rt*+]+=o[rt];
o[rt]=;
}
if(L<=(l+r)/)
{
change(ls,L,R,add);
}
if(R>(l+r)/)
{
change(rs,L,R,add);
}
su[rt]=su[*rt]+su[*rt+];
return;
}
void find(ll rt,ll l,ll r,ll L,ll R)
{
if(l>=L&&r<=R)
{
ans[c]+=su[rt];
}
else
{
if(o[rt]!=)
{
su[rt*]+=o[rt]*((r+l)/-l+);
su[rt*+]+=o[rt]*(r-(r+l)/);
o[rt*]+=o[rt];
o[rt*+]+=o[rt];
o[rt]=;
}
if(L<=(l+r)/)
{
find(ls,L,R);
}
if(R>(l+r)/)
{
find(rs,L,R);
}
su[rt]=su[*rt]+su[*rt+];
}
return;
}
int main()
{
scanf("%d %d",&n,&m);//n个基本点,m次操作
build(,,n);
for(int i=;i<=m;i++)
{
scanf("%d",&a);
if(a==)//我们要进行区间加啦
{
scanf("%lld %lld %lld",&x,&y,&k);//在x到y上加k
change(,,n,x,y,k);
// for(int i=1;i<=2*n;i++)cout<<" "<<i<<"="<<su[i];
// cout<<"\n";
// 写给需要调试的小可爱的
}
if(a==)//查询
{
c++;//个人喜欢统一输出,c记录第几次询问
scanf("%lld %lld",&x,&y);//查询x到y的和
find(,,n,x,y);
}
}
for(int i=;i<=c;i++)
{
printf("%lld\n",ans[i]);//统一输出答案
}
}

这样,一棵完完整整的基础简化版的线段树就写完了

如果你看完上面的觉得很简单,那你可以继续学习接下来的zkw线段树了

但是如果你觉得没那么简单,一定去练几个题再回来看下面的

zkw线段树不知道比递归线段树快到哪里去了,跑得嗷嗷的

看不懂就画图,手模

n是数组大小

单点修改 区间求和

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,q,ans,l,r;
int t[];
char q1[];
void build()//非递归建树,从m+1开始,多余的空间我不要了(任性)
{
for(m=;m<=n+;m<<=);
for(int i=m+;i<=m+n;i++) scanf("%d",&t[i]);
for(int i=m-;i>=;i--) t[i]=t[i<<]+t[i<<|];
}
void change(int x,int a)//这个不能再短
{
for(x+=m;x;x>>=) t[x]+=a;
}
void ask(int l,int r)
{
for(l+=m-,r+=m+;l^r^;l>>=,r>>=)
{
if(~l&)ans+=t[l^];
if(r&) ans+=t[r^];
}
printf("%d\n",ans);
}
int main()//按需填写
{
...
}

区间修改 区间求和

#include<iostream>
#include<cstdio>
using namespace std;
void build()//建树一样的
{
for(m=;m<=n+;m<<=);
for(int i=m+;i<=m+n;i++) scanf("%d",&t[i]);
for(int i=m-;i>=;i--) t[i]=t[i<<]+t[i<<|];
}
void change(int l,int r,int k)//标记不下传,永久化
{
int ln=;//左指针走了多少了
int rn=;//右指针走了多少了
int nn=;//这层的点的子树多大
for(l+=m-,r+=m+;l^r^;l>>=,r>>=,nn<<=)//这里是开区间
{
t[l]+=k*ln;
t[r]+=k*rn;
if(~l&)//左指针是左儿子,兄弟该被修改
{
add[l^]+=k;
t[l^]+=k*nn;
ln+=nn;
}
if(r&)//右指针是右儿子,同理
{
add[r^]+=k;
t[r^]+=k*nn;
rn+=nn;
}
}
for(;l;l>>=,r>>=)//加到底
{
t[l]+=k*ln;
t[r]+=k*rn;
}
}
void ask(int l,int r)
{
int ln=;
int rn=;
int nn=;
int ans=;
for(l+=m-,r+=m+;l^r^;l>>=,r>>=,nn<<=)//注意nn
{
if(add[l]) ans+=add[l]*ln;
if(add[r]) ans+=add[r]*rn;
if(~l&)
{
ans+=t[l^];
ln+=nn;
}
if(r&)
{
ans+=t[r^];
rn+=nn;
}
}
for(;l;l>>=,r>>=)//加到底
{
ans+=add[l]*ln;
ans+=add[r]*rn;
}
}
int main()
{
...
}

区间修改 区间最值

#include<iostream>
#include<cstdio>
using namespace std;
int l,r,n,q,m,ans,k,a;
int t[];
void build()
{
for(m=;m<=n+;m<<=);
for(int i=m+;i<=m+n;i++) scanf("%d",&t[i]);
for(int i=m-;i>=;i--)//区间最值有优化,建树和区间求和不一样
{
t[i]=min(t[i<<],t[i<<|]);//类似差分
t[i<<]-=t[i];
t[i<<|]-=t[i];
}
}
void change(int l,int r,int k)
{
int tmp;
for(l+=m-,r+=m+;l^r^;l>>=,r>>=)
{
if(~l&)t[l^]+=k;
if(r&) t[r^]+=k;
tmp=min(t[l],t[l^]);//继续差分
t[l]-=tmp;
t[l^]-=tmp;
t[l>>]+=tmp;
tmp=min(t[r],t[r^]);
t[r]-=tmp;
t[r^]-=tmp;
t[r>>]+=tmp;
}
for(;l!=;l>>=)//差分到底
{
tmp=min(t[l],t[l^]);
t[l]-=tmp;
t[l^]-=tmp;
t[l>>]+=tmp;
}
}
void ask(int l,int r)
{
lans=;
rans=;
if(l!=r)
{
for(l+=m,r+=m;l^r^;l>>=,r>>=)
{
lans+=t[l];
rans+=t[r];
if(~l&)lans=min(t[l^],lans);
if(r&) rans=min(t[r^],rans);
}
}
ans=min(lans+t[l],rans+t[r]);
while(s>) ans+=t[s>>=];//一定要到底
printf("%d\n",ans);
}
int main()
{
...
}

有问题的话可以问呦~虽然我也不一定会但是我会尽力解答的!

感谢阅读,求赞

线段树和zkw线段树的更多相关文章

  1. dijkstra之zkw线段树优化

    其实特别好理解,我们只要写一个数据结构(线段树)支持一下操作: 1.插入一个数\(x\). 2.查询当前数据结构中最小的数的插入编号. 3.删除插入编号为\(x\)的数. 第一眼看成可持久化了 其实就 ...

  2. ZKW线段树

    简介 zkw线段树虽然是线段树的另一种写法,但是本质上已经和普通的递归版线段树不一样了,是一种介于树状数组和线段树中间的存在,一些功能上的实现比树状数组多,而且比线段树好写且常数小. 普通线段树采用从 ...

  3. zkw线段树详解

    转载自:http://blog.csdn.net/qq_18455665/article/details/50989113 前言 首先说说出处: 清华大学 张昆玮(zkw) - ppt <统计的 ...

  4. BZOJ3173 TJOI2013最长上升子序列(Treap+ZKW线段树)

    传送门 Description 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数字,我们都想知道此时最长上升子序列长度是多少? Input ...

  5. 【POJ3468】【zkw线段树】A Simple Problem with Integers

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

  6. HDU 4366 Successor(树链剖分+zkw线段树+扫描线)

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=4366 [题目大意] 有一个公司,每个员工都有一个上司,所有的人呈树状关系,现在给出每个人的忠诚值和 ...

  7. [SinGuLaRiTy] ZKW线段树

    [SinGuLaRiTy-1007] Copyrights (c) SinGuLaRiTy 2017. All Rights Reserved. 关于ZKW线段树 Zkw线段树是清华大学张昆玮发明非递 ...

  8. 数据结构3——浅谈zkw线段树

    线段树是所有数据结构中,最常用的之一.线段树的功能多样,既可以代替树状数组完成"区间和"查询,也可以完成一些所谓"动态RMQ"(可修改的区间最值问题)的操作.其 ...

  9. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

随机推荐

  1. 关闭异常进程bat格式文件

    当电脑反应慢可能时某些进程运行导致,可将进程名称添加至bat文件中,形成脚本,快速关闭 操作方法:新建*.txt文件,将进程名以如下方式,添加至文件中,保存并修改文件名称为“*.bat”,window ...

  2. 026 Remove Duplicates from Sorted Array 从排序数组中删除重复项

    给定一个有序数组,你需要原地删除其中的重复内容,使每个元素只出现一次,并返回新的长度.不要另外定义一个数组,您必须通过用 O(1) 额外内存原地修改输入的数组来做到这一点.示例:给定数组: nums ...

  3. C#常用控件的属性以及方法(转载)

    -----以前看别人的,保存了下来,但是忘了源处,望见谅. C#常用控件属性及方法介绍 目录 1.窗体(Form) 2.Label (标签)控件 3.TextBox(文本框)控件 4.RichText ...

  4. ACdream 1427—— Nice Sequence——————【线段树单点更新,区间查询】

    Nice Sequence Time Limit: 4000/2000MS (Java/Others)    Memory Limit: 128000/64000KB (Java/Others) Su ...

  5. cucumber的疑问解答

    在cucumber的自动化测试框架下面,在一个steps文件中定义的@page对象,可以在其他的不同的steps文件中调用,在整个的场景生命周期中都是有效的 原因:cucumber开始执行时,一次性把 ...

  6. pscp no such file or directory

    背景:在WINDOWS10 上传一个文件 到 Centos 7中 工具:pscp 用法: pscp.exe -C e:\tinyfox\site\wwwroot\cdms\projecttemplat ...

  7. 前端JS电商放大镜效果

    前端JS电商放大镜效果: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...

  8. [转]Tomcat日志详解

    Tomcat下相关的日志文件: 1.Cataline引擎的日志文件,文件名为catalina.{date}.log 2.Tomcat下内部代码丢出的日志,文件名为localhost.{date}.lo ...

  9. Nodejs入门边读边想边记(-)

    Node入门>>一本全面的Node.js教程网站地址:http://www.nodebeginner.org/index-zh-cn.html 本文记录我在阅读上面这个网站的过程中得到的一 ...

  10. [QualityCenter]设置工作流脚本-根据某字段是否包含指定字符串来判断其他字段的选值

    需求:当在创建或更改值时,自动判断A字段是否包含B值,然后自动填写相应的内容. 如以下例子: 在脚本编辑器新建一个函数TestPlan_Test_New,然后编写脚本如下: '通过主题判断项目内容   ...