其他分享
首页 > 其他分享> > 线段树(3)

线段树(3)

作者:互联网

例题
2.
Can you answer on these queries III

题 目 简 述 题目简述 题目简述

给定长度为N的数列A,以及M条指令 ( N ≤ 500000 , M ≤ 100000 ) (N≤500000, M≤100000) (N≤500000,M≤100000),每条指令可能是以下两种之一:

“2 x y”,把 A[x] 改成 y。

“1 x y”,查询区间 [x,y] 中的最大连续子段和,即区间 [ l , r ] [l,r] [l,r]内,连续累加和最大的。 对于每个询问,输出一个整数表示答案。

//input
5 3
1 2 -3 4 5
1 2 3
2 2 -1
1 3 2
//output
2
-1

思 路 思路 思路
线段树的整体思路不变,我们只需在建树和单点修改时变化一下从下往上传递的信息即可,多维护三个信息,即连续最大子段和dat,左端的连续最大子段和lmax,以及右端的连续最大子段和rmax

t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));

PS:1.这里建议写一个更新函数update(int bh),每次更新值时调用次函数即可
2.线段树一般有多组输入输出,建议用快读或 s c a n f , p r i n t f scanf,printf scanf,printf。

void update(int bh)
{
	t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
	t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
	t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
	t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));
}

正 解 代 码 正解代码 正解代码

#include <bits/stdc++.h>
using namespace std;
const int MAXN=500086;
struct tree{
	int l,r,lmax,sum,rmax,dat;
}t[4*MAXN];
int n,a[MAXN],m;
void update(int bh)
{
	t[bh].sum=t[bh*2].sum+t[bh*2+1].sum;
	t[bh].lmax=max(t[bh*2].lmax,t[bh*2+1].lmax+t[2*bh].sum);
	t[bh].rmax=max(t[bh*2+1].rmax,t[bh*2].rmax+t[2*bh+1].sum);
	t[bh].dat=max(t[bh*2].dat,max(t[bh*2+1].dat,t[bh*2].rmax+t[bh*2+1].lmax));
} 
void build(int bh,int l,int r)
{
	t[bh].l=l;	
	t[bh].r=r;	
	if(l==r)
	{
		t[bh].sum=a[l];
		t[bh].dat=a[l];
		t[bh].lmax=a[l];
		t[bh].rmax=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(2*bh,l,mid);
	build(2*bh+1,mid+1,r);
	update(bh);
}
void change(int bh,int x,int summ)
{
	
	if(t[bh].l==t[bh].r)
	{
		t[bh].sum=summ;
		t[bh].dat=summ;
		t[bh].lmax=summ;
		t[bh].rmax=summ;
		return ;
	}
	int mid=(t[bh].l+t[bh].r)/2;
	if(x<=mid) change(bh*2,x,summ);
	else change(bh*2+1,x,summ);
	update(bh);
}
tree ask(int bh,int l,int r)
{
	if(l<=t[bh].l&&r>=t[bh].r)
	{
		return t[bh];
	}
	int mid=(t[bh].l+t[bh].r)/2;
	int w=-MAXN*10;
	if(mid>=r) return ask(bh*2,l,r);
	if(mid<l) return ask(bh*2+1,l,r);
	else {
		tree ans,left,right;
        left=ask(bh*2,l,r);
        right=ask(bh*2+1,l,r);
        ans.sum=left.sum+right.sum;
        ans.dat=max(max(left.dat,left.rmax+right.lmax),right.dat);
        ans.lmax=max(left.lmax,left.sum+right.lmax);
        ans.rmax=max(right.rmax,right.sum+left.rmax);
        return ans;
	}
}
int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	build(1,1,n);
	int a,b,c;
	while(m--)
	{
		scanf("%d%d%d",&a,&b,&c);
		if(a==2) change(1,b,c);
		else 
		{
			if(b>c) swap(b,c);
			printf("%d\n",ask(1,b,c).dat);	
		}
	}
    return 0;
}

相信同志们经过以上的训练已经对线段树有了足够的认识。下面让我们进一步了解它。

延迟标记/懒标记(lazy tag)

在进行修改操作时,无论是单点修改,还是区间修改,都需要找到完全包含修改区间 [ q l , q r ] [ql,qr] [ql,qr]的节点 x x x并对其本身及其子树中的所有节点进行更新,但如果在查询答案时,答案与修改操作完全无关,那么用来进行修改的时间就会被浪费(血亏),所以我们在执行修改指令时,可以增加一个标记,表示“此节点已经被修改,但其子节点未被修改”,在后面的查询中,我们再检查此节点是否具有标记,有则更新此节点的值并将标记传递给它的子节点,然后清除标记。

下面以区间最大值和区间和为例

void spread(int p)
{
	if(t[p].add)
	{
		t[p*2].maxx+=t[p].add;
		t[p*2+1].maxx+=t[p].add;
		t[p*2].add+=t[p].add;
		t[p*2+1].add+=t[p].add;
		t[p].add=0;
	}
}

复杂度
这样的情况下,对任意节点的修改都延迟到了“在后续查询操作中进入父节点”,每条查询或修改指令的时间复杂度都被降低到了 O ( l o g N ) O(logN) O(logN)。

标签:int,bh,线段,rmax,dat,lmax,sum
来源: https://blog.csdn.net/zero_orez6/article/details/115263410