线段树学习之单点更新与区间更新

经过一星期的学习,线段树算是摸到了皮毛,大致学会了单点更新与区间更新,区间更新感觉还是有点略难
,学到新东西还是很快乐的

定义

还是定义先行。线段树作为高级数据结构,要想了解基本内容,需要对数据结构的树有一定的了解。线段树属于二叉搜索树,将一个大区间进行划分,划分为单元小区间,每个单元区间又对应树中的一个叶子节点。
如果感觉难以理解,那么希望下面这张图有助于理解。对于每个非叶子节点,左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b],子节点的数目即为整个线段区间的长度。

性质

  • 时间复杂度: O(logN),较小,可以快速查询某一结点出现的次数,查询区间长度等很方便
  • 空间复杂度: 2N,较大,并且在实际应用中需要开4倍的数组防止出现越界(这个很重要,我又一次就因为这个没注意,wa了一次?,找半天没找到原因)

基本用法

  • 单点更新、替换…
  • 成段替换、区间更新…
  • 区间求和、区间操作…

以上是我这个周学到的基本用法(太菜了orz)

例题

单点:
hdu1166:敌兵布阵
hdu 1754:I Hate It
区间:
hdu 1698: Just a Hook
poj 3468: A Simple Problem with Integers

直接上代码吧orz(按顺序)
敌兵布阵

#include<iostream>
using namespace std;

int tree[200000];

//建树 
void BuildTree(int l,int r,int k) {
	int mid;
	if(l == r)	{
		scanf("%d",&tree[k]);
		return ;	
	}
	mid = (l+r)/2;
	BuildTree(l,mid,2*k);		//左儿子 
	BuildTree(mid+1,r,2*k+1);	//右儿子
	tree[k]	= tree[2*k]+tree[2*k+1];	//初始化父代节点 
}

//单点更新
void AddNode(int l,int r,int k,int a,int b) {
	int mid;
	if(l==r){
		tree[k]+=b;				//单点操作
		return ; 
	}
	mid = (l+r)/2;
	if(a<=mid){
		AddNode(l,mid,2*k,a,b);		//左子树单点更新
	}
	else{
		AddNode(mid+1,r,2*k+1,a,b);
	} 
	tree[k] = tree[2*k]+tree[2*k+1];
}

//查询
int Query(int l,int r,int k,int a,int b) {
	int mid;
	if(a<=l && b>=r){
		return tree[k];
	}
	mid = (l+r)/2;
	int ans = 0;
	if(a<= mid){
		ans += Query(l,mid,2*k,a,b);
	}
	if(b>mid)	{
		ans += Query(mid+1,r,2*k+1,a,b);
	}
	return ans;
}

int main(){
	int Case,t;
	char str[11];
	scanf("%d",&t);
	for(Case = 1;Case<=t;Case++){
		int n;
		scanf("%d",&n);
		BuildTree(1,n,1);
		printf("Case %d:\n",Case);
		while(~scanf("%s",str)){
			if(str[0] == 'E'){
				break;
			}
			int x,y;
			scanf("%d%d",&x,&y);
			if(str[0] == 'Q'){
				int ans = Query(1,n,1,x,y);
				cout << ans << endl;
			}
			if(str[0] == 'S'){
				AddNode(1,n,1,x,-y);
			}
			if(str[0] == 'A'){
				AddNode(1,n,1,x,y);
			}
		}
	}
	return 0;
}

i hate it

#include<iostream>
#include<string.h> 
using namespace std;

int tree[400010];

//建树 
void BuildTree(int l,int r,int k) {
	int mid;
	if(l == r)	{
		scanf("%d",&tree[k]);
		return ;	
	}
	mid = (l+r)>>1;
	BuildTree(l,mid,k<<1);		//左儿子 
	BuildTree(mid+1,r,k<<1|1);	//右儿子
	tree[k]	= max(tree[k<<1],tree[k<<1|1]);	//初始化父代节点 
}

//单点更新
void AddNode(int l,int r,int k,int a,int b) {
	int mid;
	if(l==r){
		tree[k]=b;				//单点操作
		return ; 
	}
	mid = (l+r)>>1;
	if(a<=mid){
		AddNode(l,mid,k<<1,a,b);		//左子树单点更新
	}
	else{
		AddNode(mid+1,r,k<<1|1,a,b);
	} 
	tree[k] = max(tree[k<<1],tree[k<<1|1]);
}

//查询
int Query(int l,int r,int k,int a,int b) {
	int mid;
	if(a<=l && b>=r){
		return tree[k];
	}
	mid = (l+r)>>1;
	int ans = 0;
	if(a<= mid){
		ans = max(ans,Query(l,mid,k<<1,a,b)) ;
	}
	if(b>mid){
		ans = max(ans,Query(mid+1,r,k<<1|1,a,b));
	}
	return ans;
}

int main(){
	int n,m;
	char str[2];
	while(~scanf("%d%d",&n,&m)){
		BuildTree(1,n,1);
		int x,y;
		for(int i = 1;i<=m;i++){
			scanf("%s%d%d",str,&x,&y); 
			if(str[0]=='U'){
				AddNode(1,n,1,x,y);
			}
			else{
				int ans = Query(1,n,1,x,y);
				printf("%d\n",ans);
			}
		}
	}
	return 0;
}

区间更新:区间更新比单点更新难以理解,而且也多了一个lazy数组操作,看见lazy你就会明白,这个数组存在的意义就在于,,所有还有一个名字(懒惰标记或延迟更新标记)。每次更新到区间后,就会存放在lazy数组里,等到需要往下更新更小区间的时候,就把两次的值一起更新下去.

Just a Hook

#include<cstdio>
#include<iostream>
#include<cstring> 
using namespace std;
const int maxn=1e5+5;

int lazy[maxn*4]; 
int tree[maxn*4];

void Push_Down(int l,int r,int k){
    if(lazy[k]){
        lazy[k<<1]=lazy[k];
		lazy[k<<1|1]=lazy[k];
        int mid=(l+r)>>1;
        tree[k<<1]=(mid-l+1)*lazy[k];
        tree[k<<1|1]=(r-mid)*lazy[k];
        lazy[k]=0;
    }   
}

void BuildTree(int l,int r,int k){
    if(l==r){
        tree[k]=1;
        return ;
    }
    int mid=(l+r)>>1;
    BuildTree(l,mid,k<<1);
    BuildTree(mid+1,r,k<<1|1);
    tree[k]=tree[k<<1]+tree[k<<1|1];
}

void AddNode(int l,int r,int k,int a,int b,int flag){
    if(a<=l&&b>=r){
        lazy[k]=flag;
        tree[k]=(r-l+1)*flag;
        return;
    }
    else{
        int mid=(l+r)>>1;
        Push_Down(l,r,k);
        if(a<=mid)AddNode(l,mid,k<<1,a,b,flag);
        if(b>mid)AddNode(mid+1,r,k<<1|1,a,b,flag);
        tree[k]=tree[k<<1]+tree[k<<1|1];
    }
}
int main(){
    int t,n,m;
    int x,y,z;
    scanf("%d",&t);
    for(int k=1;k<=t;k++){
        scanf("%d",&n);
        memset(lazy,0,sizeof(lazy));
        BuildTree(1,n,1);
        scanf("%d",&m);
        for(int i=1;i<=m;i++){                    
            scanf("%d%d%d",&x,&y,&z);
            AddNode(1,n,1,x,y,z);
        }
        printf("Case %d: The total value of the hook is %d.\n",k,tree[1]);
    }
    return 0;
}

A Simple Problem with Integers

#include<iostream>
#include<cstdio>
using namespace std;
const int maxn = 1e6+5;

long long n,m,tree[maxn<<2],lazy[maxn<<2],ans;

void Push_Down(int l,int r,int k){
	  int mid = (l + r)>>1;
	  lazy[k<<1] += lazy[k];
	  lazy[k<<1|1] += lazy[k];
	  tree[k<<1] += (mid - l + 1)*lazy[k];
	  tree[k<<1|1] += (r - mid)*lazy[k];
	  lazy[k] = 0;
} 

void BuildTree(int l,int r,int k){
	if(l==r){
		scanf("%lld",&tree[k]);
		return;
	}
	int mid = (l + r)>>1;
	BuildTree(l,mid,k<<1);
	BuildTree(mid+1,r,k<<1|1);
	tree[k] = tree[k<<1] + tree[k<<1|1];
}

void Query(int a,int b,int l,int r,int k){
	if(a<=l&&r<=b){
		ans+=tree[k];
	     return;
	}
	if(lazy[k]){
		Push_Down(l,r,k);
	}
	int mid = (l+r)>>1;
	if(a<=mid){
		Query(a,b,l,mid,k<<1);
	}
	if(mid<b){
		Query(a,b,mid+1,r,k<<1|1);
	}
}
void AddNode(int a,int b,int c,int l,int r,int k){
	if(l>=a&&r<=b){
		tree[k] += (r-l+1)*c;
		lazy[k] += c;
		return; 
	}
	if(lazy[k]){
		Push_Down(l,r,k);
	}
	int mid = (l + r)>>1;
	if(a<=mid){
		AddNode(a,b,c,l,mid,k<<1);
	}
	if(b>mid){
		AddNode(a,b,c,mid+1,r,k<<1|1);
	}
	tree[k] = tree[k<<1] + tree[k<<1|1];
}
int main(){
	long long n,m;
	scanf("%lld%lld",&n,&m);
	BuildTree(1,n,1);
	char str[2];
	for(int i = 1;i <= m;i++){
		cin >> str;
		if(str[0]=='Q'){
			int a,b;
			scanf("%d%d",&a,&b);
			ans = 0;	
			Query(a,b,1,n,1);
			printf("%lld\n",ans);
		}
		else if(str[0]=='C'){
			int a,b,c;
			scanf("%d%d%d",&a,&b,&c);
			AddNode(a,b,c,1,n,1);	
		}
	} 
	return 0;
}

一口气看完后,有没有觉得这些代码长得很像?没错他们真的长得很像,线段树四道题写完,第一感受,线段树的简单操作模式还是相对固定的,板子直接改。第二感受,好像没有那么好改(还是我太菜了),bug改起来很难受,真的很难受。写博客的时候看到了一篇很好的博客,给大家分享一下,博主讲的很清楚明白。
传送门
以后还有学习的有关知识,我会努力挖坑填坑的。

发布了13 篇原创文章 · 获赞 1 · 访问量 455

猜你喜欢

转载自blog.csdn.net/amino_acid0617/article/details/102530196