【线段树】ZKW线段树浅谈

(学了学一般的线段树,还学了学线段树合并还有扫描线什么的,然后就就看到有一个比较有意思的线段树,,就学了一下)

这里,,警告!

zkw这东西,犹如鸡肋之令,食之无味,弃之可惜,没有太大的作用,本菜鸡就是敲一下板子了解一下即可。。。。。

进入正题,,,,
安利博客:洛谷日报SinGuLaRiTyJudge

关于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;
}

关于线段树,其实还有很多东西,比如多维线段树、多叉线段树、……不过因为本人这个蒟蒻,而且没有什么重要的作用就先不学了,,,
关于线段树的学习,尽管浪费的时间比较长,但是呢,还是比较顺利的,还缺少一些题目的巩固和练习,,,继续好好写题吧,,,,,ヽ(ー_ー)ノ

Continue……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值