树状数组+线段树

这篇博客介绍了树状数组和线段树的数据结构,并探讨了它们在处理单点修改和区间和询问等问题中的实际应用。通过具体的题目——2022ICPC程序设计竞赛中的'I Hate It'问题,展示了如何使用这两种数据结构来解决区间最值查询和成绩更新。此外,还给出了另一道类似问题3243:A Simple Problem with Integers,进一步阐述了区间查询和区间更新的操作。
摘要由CSDN通过智能技术生成

树状数组

在这里插入图片描述

 在这里插入图片描述

实际应用

单点修改 + 区间和询问

//树状数组 (前缀和、区间、点更新) 
const int N=10005;
int n,a[N],b[N];//树状数组b 
int lowbit(int i)
{
	return (-i)&i;
}
void add(int i,int z)//点更新,a[i]+z 
{
	for(i<=n;i+=lowbit(i))//更新所有后继 
	{
		b[i]+=z;
	}
}
int sum(int i)//前缀和 ,a[1]...a[i]
{
	int s=0;
	for(;i>0;i-=lowbit(i))//累加所有前驱 
	s+=c[i];
	return s;
} 

线段树

求某区间最值或更新点

/线段树(区间更新)开4n区间
/*创建
1.若叶子节点,节点的最值即对应元素值
2.非叶子节点,递归创建左子树和右子树
3.节点区间的最值==该节点左右子树最值的最大值
*/
#define lc k<<1//左孩子存储下标 
#define rc k<<1+1// 右孩子存储下标
const int N=10005;
const int inf=0x3f3f3f3f;
int n,a[N];
struct node{
	int l,r,mx;
}tree[N*4];

void build(int k,int l,int r)//创建线段树 
{
	tree[k].l=l;
	tree[k].r=r;
	if(l==r){//叶子节点 
		tree[k].mx=a[l];
		return ;
	}
	int mid=(l+r)/2;
	build(lc,l,mid);//左孩子
	build(rc,mid+1,r);//右孩子
	//更新最值
	tree[k].mx=max(tree[lc].mx,tree[rc].mx); //回归更新 
} 
void updata(int k,int i,int v)//点更新 
{
	if(tree[k].l==tree[k].r&&tree[k].l==i)//叶子节点
	 {tree[k].mx=v
	 return ;}
	 int mid=(tree[k].l+tree[k].r)/2;
	 if(i<=mid)//左子树
	 updata(lc,i,v);
	 else 
	 updata(rc,i,v);
	 //回归更新最值 
	 tree[k].mx=max(tree[lc].mx,tree[rc].mx); 
} 
//查询一个区间的最值
/*若节点所在的区间被【l,r】覆盖,则返回该节点最值
2.判断是在左子树还是右子树
3.返回最值*/
 int query1(int k,int l,int r)
 {
 	if(tree[k].l>=l&&tree[k].r<=r)//区间被覆盖
	 return tree[k].mx ;
	 int mid=(tree[k].l+tree[k].r)/2;
	 int M=-inf;//负无穷 //必须局部 
	 if(l<=mid)//左区间查找
	 M=max(M,query(lc,l,r))//左子树查询
	 if(r>=mid+1)
	 M=max(M,query(rc,l,r)) //右子树找
	 return M; 
	  
 }
 
 //区间缩小 
 int query2(int k,int l,int r)
 {
 	if(tree[k].l==l&&tree[k].r==r)//区间相等
	 return tree[k].mx;
	 int mid=(tree[k].l+tree[k].r)/2;
	 int M=-inf;//负无穷 //必须局部 
	 if(r<=mid)//区间全在左区间 
	  return query(lc,l,r);//左子树查询
	 else if(l>=mid+1)//区间全在右区间 
	 return query(rc,l,r) ;//右子树找
	 else {//跨区间 
	 	return max(query2(lc,l,mid),query2(rc,mid+1,r));
	 }
	  
 }

例题

 I Hate ItProblem Detail | 2022ICPC 程序设计竞赛基础第九周周赛 (jxnu.edu.cn)

描述:

很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。

输入:

本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 N 和 M ( 0<N<=200000,0<M<5000 ),分别代表学生的数目和操作的数目。
学生ID编号分别从1编到N。
第二行包含N个整数,代表这N个学生的初始成绩,其中第i个数代表ID为i的学生的成绩。
接下来有M行。每一行有一个字符 C (只取'Q'或'U') ,和两个正整数A,B。
当C为'Q'的时候,表示这是一条询问操作,它询问ID从A到B(包括A,B)的学生当中,成绩最高的是多少。
当C为'U'的时候,表示这是一条更新操作,要求把ID为A的学生的成绩更改为B。

输出:

对于每一次询问操作,在一行里面输出最高成绩。

样例输入:

5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5

复制

样例输出:

5
6
5
9
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=200002;
int s[N];

const int inf=0x3f3f3f3f;
struct node{
	int l,r,mx;
}tree[4*N];

void build(int k,int l,int r)
{
	tree[k].l=l,tree[k].r=r;
	if(l==r)
	{
		tree[k].mx=s[l];
		return ;
	}
	int mid=(l+r) /2;
	build(k*2,l,mid);
	build(k*2+1,mid+1,r);
	tree[k].mx=max(tree[k*2].mx,tree[(k*2)+1].mx);
}
void updata(int k,int i,int v)
{
	if(tree[k].l==tree[k].r)
	{
		tree[k].mx=v;
		return ;
	}
	int mid=(tree[k].l+tree[k].r)/2;
	if(i<=mid)
	updata(k*2,i,v);
	else if(i>=mid+1)
	updata((k*2)+1,i,v);
	tree[k].mx=max(tree[k*2].mx,tree[(k*2)+1].mx);
}
int query(int k,int l,int r)
{
	if(tree[k].l>=l&&tree[k].r<=r)
	return tree[k].mx;
	int M=-inf;
	int mid=(tree[k].l+tree[k].r)/2;
	if(l<=mid)
	    M=max(M,query(k*2,l,r));
	if(r>=mid+1)
	   M=max(M,query((k*2)+1,l,r));
	return M;
}
/*
int query(int k,int l,int r)
{
	if(tree[k].l==l&&tree[k].r==r)
	return tree[k].mx;
	int mid=(tree[k].l+tree[k].r)/2;
	if(r<=mid)
	return query(k*2,l,r);
	else if(l>mid)
	return query(k*2+1,l,r);
	else return max(query(k*2,l,mid),query(k*2+1,mid+1,r));
}*/
int main()
{
	int n,m;
	while(scanf("%d %d",&n,&m)!=EOF)
	{
	for(int i=1;i<=n;i++)
	scanf("%d",&s[i]);
	build(1,1,n);
	int a,b;
	char c;
	while(m--)
	{   getchar();
		scanf("%c %d %d",&c,&a,&b);
		if(c=='Q')
		 printf("%d\n",query(1,a,b));
		else 
		updata(1,a,b);
	}
}
	return 0;
}

区间查询与区间更新

点修改

/*线段树的区间查询与更新
 */
 struct node{
 	int l,r,mx;
 }tree[N];
 int a[N]; 
 #define lc k<<1
 #define rc k<<1|1
 void build(int k,int l,int r)//建立树 
 {
 	tree[k].l=l,tree[k].r=r;
 	if(l==r)
 	{
 		tree[k].mx=a[l];
 		return ;
 	}
 	int mid=(l+r)/2;
 	build(rc,mid+1,r);
 	build(lc,l,mid);
 	tree[k].mx=tree[rc].mx+tree[lc].mx;
 } 
 
 void updata(int k,int i,int v)
 {
 	if(tree[k].l==tree[k].r)
	{
	tree[k].mx=v;
	a[l]=v;
 	return ;
	 }
	 int mid=(tree[k].l+tree[k].r)/2;
	 if(i>mid) updata(rc,i,v);
	 else updata(lc,i,v);
	 tree[k].mx=tree[rc].mx+tree[lc].mx;
 }
 
int query(int k,int l,int r)
{
	if(r<tree[k].l||tree[k].r<l)//所求区间不在该节点所维护的区间内 
	return 0;
	if(tree[k].l==tree[k].r) 
	return tree[k].mx;//为叶子节点 
	int mid=(tree[k].l+tree[k].r)/2;
	int suml=query(lc,l,mid);
	int sumr=query(rc,mid+1,r);
	return suml+sumr;
}

区间修改

例题

3243:A Simple Problem with Integers

总时间限制: 

5000ms

单个测试点时间限制: 

2000ms

内存限制: 

131072kB

描述

You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.

输入

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.

输出

You need to answer all Q commands in order. One answer in a line.

样例输入

10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4

样例输出

4
55
9
15

OpenJudge - 3243:A Simple Problem with Integers

#include<iostream>
#include<cstdio>
using namespace std;
const int N=100002;
typedef long long in;
in a[N];

struct node{
	in l,r,mx,lazy;//懒惰标记 
}tree[N*4];
#define rc k<<1|1
#define lc k<<1
void build(in k,in l,in r)
{
	tree[k].l=l,tree[k].r=r,tree[k].lazy=0;
	if(l==r)
	{
		tree[k].mx=a[l];
		return ;
	}
	in mid=(l+r)/2;
	build(lc,l,mid);
	build(rc,mid+1,r);
	tree[k].mx=tree[rc].mx+tree[lc].mx;
}
void down(in k) 
{
	tree[lc].lazy+=tree[k].lazy;
	tree[rc].lazy+=tree[k].lazy;
	tree[lc].mx+=tree[k].lazy*(tree[lc].r-tree[lc].l+1);
	tree[rc].mx+=tree[k].lazy*(tree[rc].r-tree[rc].l+1);
	tree[k].lazy=0;
} 
void updata(in k,in l,in r,in v)
{
	if(tree[k].l>=l&&tree[k].r<=r)
	{
	tree[k].mx+=(tree[k].r-tree[k].l+1)*v;
	tree[k].lazy+=v;//到达一个区域,不用到达叶子结点 
 	return ;
	 }
	 if(tree[k].lazy)down(k);
	 in mid=(tree[k].l+tree[k].r)/2;
	 if(r>mid) updata(rc,l,r,v);
	 if(l<=mid)updata(lc,l,r,v);
	 tree[k].mx=tree[rc].mx+tree[lc].mx;
}
in query(in k,in l,in r)
{
   if(tree[k].l>=l&&tree[k].r<=r)
	return tree[k].mx;
	if(tree[k].lazy)
	down(k);//询问到之前需要修改的区间 
	in mid=(tree[k].l+tree[k].r)/2;
	in suml,sumr;
	suml=sumr=0;
	if(l<=mid)
	    suml=query(k*2,l,r);
	if(r>=mid+1)
	   sumr=query((k*2)+1,l,r);
	return suml+sumr;
}
int main()
{
	in n,m;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
	scanf("%lld",&a[i]);
	build(1,1,n);
	//for(int i=1;i<=4*n;i++)
	//printf("p[%d]=%d\n",i,tree[i].mx);
	char c;
	in a,b,v;
	while(m--)
	{
		getchar();
		scanf("%c",&c);
		if(c=='Q'){
			scanf("%lld%lld",&a,&b);
			printf("%lld\n",query(1,a,b));
		}
		else {
			scanf("%lld%lld%lld",&a,&b,&v);
			updata(1,a,b,v);
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值