(学了学一般的线段树,还学了学线段树合并还有扫描线什么的,然后就就看到有一个比较有意思的线段树,,就学了一下)
这里,,警告!
zkw这东西,犹如鸡肋之令,食之无味,弃之可惜,没有太大的作用,本菜鸡就是敲一下板子了解一下即可。。。。。
进入正题,,,,
安利博客:洛谷日报,SinGuLaRiTy,Judge
关于zkw线段树
zkw线段树,就是从底到上建线段树,抛弃掉之前的由上到下不断递归的建树方法,这样的话就比较快了,在查询的时候也是从底到上的,由于没有递归,所以就是常数比较小啊,可以看一下同一道题线段树和zkw线段树之间的速度,(下面有对比),这就会发现,无论从代码长短,跑的速度来看,树状数组≈zkw线段树<线段树,但是在不考虑有运算优先级的情况下,树状数组吊打全场,所以:
**zkw有云:**树状数组究竟是什么?就是省掉一半空间后的线段树加上中序遍历。
(但是呢,,如果写线段树写多了,就觉得其实差不多,如果线段树TLE了,一定不是以为这个原因,应该 不会有什么毒瘤出题人会卡这样的常数,,)
下面就放两道模板吧,重点就放在注释上了。
单点修改+区间查询
题目:线段树练习
题目链接:线段树练习
代码:
#include<bits/stdc++.h>
#define M 261244//这里根据那个公式可以推出来1e5的最大M就是2e5(乘二就行)
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=5e5+7;//竟然让样例跑RE了,,, 我说怎么一直调不出来,这个zkw范围是要很大的
int n,m,tr[sea];
void alter(int x,int y)
{
for(tr[x+=M]+=y,x>>=1;x;x>>=1)//这里hzwer一下就代替了洛谷日报上的很多无用的做法,但是这是时候需要提前定义一下M
tr[x]=tr[x<<1]+tr[x<<1|1];
//zkw由于和之前的递归版的线段树不一样,zkw是从底部向上面进行建树,所以刚开始是需要个比较大的范围进行圈定。
//所以这样就是先把线段树填充成满二叉树(堆式存储),之后就可以直接找到叶节点,然后回溯上去了
}
int ask(int x,int y)
{
int s=0;
for(x=x+M-1,y=y+M+1;x^y^1;x>>=1,y>>=1)
{
if(x&1^1) s+=tr[x^1]; //加上左指针的右儿子
if(y&1) s+=tr[y^1]; //加上右指针的左儿子
}
return s;
}
int main()
{
n=read();
for(int i=1,x;i<=n;i++) x=read(),alter(i,x);
m=read();
for(int i=1;i<=m;i++)
{
int s=read(),x=read(),y=read();
if(s==1) alter(x,y); else printf("%d\n",ask(x,y));
}
return 0;
}
区间修改+区间查询
题目:【模板】线段树 1
题目链接:【模板】线段树 1
这个题,用普通线段树写的话:
要是用zkw写的话:
很明显跑的贼快,,,1.5倍的差距,,
(尽管没有什么用,但是可以优化常数,,,)
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=1e5+7;
struct hit{int w,lazy;}tr[sea*4];
int n,m,M;
void alter(int x,int y,int z)//区间修改
{
int ls=0,rs=0,len=1;
for(x+=M-1,y+=M+1;x^y^1;x>>=1,y>>=1,len<<=1))//有底向上对树进行遍历
{
if(x&1^1) tr[x^1].lazy+=z,ls+=len;//更新左指针的右儿子以及懒标记,用ls进行记录,
if(y&1) tr[y^1].lazy+=z,rs+=len;// 更新右指针的左儿子以及懒标记, 用rs进行记录
tr[x>>1].w+=z*ls,tr[y>>1].w+=z*rs;//根据ls和rs进行对区间和的更新
}w
for(ls+=rs,x>>=1;x;x>>=1) tr[x].w+=z*ls;//然后再左右加起来对父亲节点直至最高点进行更新
}
int ask(int x,int y)//区间查询
{
int ls=0,rs=0,len=1,ans=0;
for(x+=M-1,y+=M+1;x^y^1;x>>=1,y>>=1,len<<=1)//有底向上对树进行遍历
{
if(x&1^1) ans+=tr[x^1].w+len*tr[x^1].lazy,ls+=len;
if(y&1) ans+=tr[y^1].w+len*tr[y^1].lazy,rs+=len;
if(tr[x>>1].lazy) ans+=tr[x>>1].lazy*ls;
if(tr[y>>1].lazy) ans+=tr[y>>1].lazy*rs;
}
for(ls+=rs,x>>=1;x;x>>=1) if(tr[x].lazy) ans+=tr[x].lazy*ls;
return ans;
}
signed main()
{
n=read();m=read();
for(M=1;M<=n;M<<=1);
for(int i=M+1;i<=M+n;i++) tr[i].w=read();
for(int i=M-1;i>=1;i--) tr[i].w=tr[i<<1].w+tr[i<<1|1].w;
for(int i=1;i<=m;i++)
{
int s=read(),x=read(),y=read(),z;
if(s==1) z=read(),alter(x,y,z);
else printf("%lld\n",ask(x,y));
}
return 0;
}
关于线段树,其实还有很多东西,比如多维线段树、多叉线段树、……不过因为本人这个蒟蒻,而且没有什么重要的作用就先不学了,,,
关于线段树的学习,尽管浪费的时间比较长,但是呢,还是比较顺利的,还缺少一些题目的巩固和练习,,,继续好好写题吧,,,,,ヽ(ー_ー)ノ