线段树-hdu1754+loj1164

线段树能把区间上的任意一条长度为 L 的线段都分成不超过 2log L条线段。
因此对于查询或修改某个长度为 L的区间,我们只要在分解 出来的线段上操作,然后在合并这几个区间信息即可。
所以对于一般的操作,单次时间复杂度都是 O(log n)
在这里插入图片描述

  • 每个节点维护一个闭区间[l,r] (l<=r)的信息。
  • 根节点表示[1,n]的信息。
  • 如果l==r就是叶子结点。
  • 如果了l<r就是内部节点,他有两个子节点[l,(l+r)/2],[(l+r)/2+1,r].
    用1表示根节点。
    下标为x的左子节点下标为2x,右子节点下标为2x+1.
    用sum[x]表示x代表的区间所有数的和。
    对于叶子节点,l==r,sum[x]=a[r];
//线段树的链表存储
struct node{
	int left,right,value;
	node *lchild,*rchild;
};
/*
left和right表示节点区间
value维护这个区间的信息,比如区间和等;
每个节点同时维护两个孩子的指针。
坏处就是指针要动态申请,由于寻址不连续,所以效率不高。
*/
//数组存储
/*
数组存储也有一点不好,因为线段树并是真正的完全二叉。
最后一层可能很空。且节点的数量以达到 2n 个。
因此维护长度为 n的序列,用数组存线段树话最好要开 到 4*n的
*/
void build(int l,int r,int x)
{
	if(l==r)//叶子节点
	{
		sum[x]=a[r];
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,x<<2);
	build(mid+1,r,x<<1|1);
	update(x);//更新信息
}
void update(int x)//更新信息
{
	sum[x]=sum[x<<2]+sum[x<<2|1];
}
/*
查询的过程也是自顶向下。
假设询问区间是 [A, B] ,现在所的节点表示区间为 [l, r]
• 如果 A <= l<= r<= B  ,那么直接返回当前节点的 sum , 不用递归下去。
• 否则,我们需要判断不递归到两个子节点上询问。
线段树的查询
假设询问区间是 [A, B] ,现在所的节点表示区间为 [l, r]
• 计算 mid = (l + r) / 2 mid = (l + r) / 2 ,左子节点的区间为 [l, mid] , 右子节点的区间为 [mid+1, r]. [mid+1, r].
• 如果 A<= mid ,即询问区间与左子节点有重合需要递归 到左子节点。
• 如果 B >= mid + B >= mid +1,即询问区间与右子节点有重合,需要 递归到右子节点。
• 递
*/
int query(int A,int B,int l,int r,int x)//线段树查询
{
	if(A<=l&&r<=B)
		return sum[x];
	int mid=(l+r)>>2,ans=0;
	if(A<=mid)
		ans+=query(A,B,l,mid,x<<2);
	if(B>mid)
		ans+=query(A,B,mid+1,r,x<<2|1);
	return ans;
}
int update(int pos,int v,int l,int r,int x)//对单个元素(pos)的修改,单个元素位于叶子节点
{
	if(l==r)//找到需要修改的叶子节点
	{
		sum[x]=v;
		return;
	}
	int mid=(l+r)>>1;
	if(pos<=mid)//pos在左子节点
		update(pos,v,l,mid,x<<1);
	else
		update(pos,v,mid+1,r,x<<1|1);
	sum[x]=sum[x<<1]+sum[x<<1|1];//sum值发生改变
}
//区间修改lazy修改方法
#define ls (x<<1)//左子节点
#define rs	(x<<1|1)//右子节点
void pushdown(int x,int l,int r)//下传标记
{
	int mid=(l+r)>>1;
	if(tag[x])
	{
		tag[lr]=tag[x];
		tag[rs]=tag[x];
		sum[ls]=(mid-l+1)*tag[x]; 
		sum[rs]=(r-mid)*tag[x];
		tag[x]=0;//已经下传标记清0
	}
	return}
void update(int x,int A,int B,int l,int r,int v)//区间修改[l,r]为大区间,[A,B]为修改区间,x为根结点
{
	if(l<=A&&r<=B)
	{
		tag[x]=v;
		sum[x]=v*(r-l+1);
		return;
	}
	pushdown(x,l,r);
	int mid=(l+r)>>1;
	if(A<=mid)	update(ls,A,B,l,mid,v);
	if(mid<B)	update(rs,A,B,mid+1,r,v);
	sum[x]=sum[ls]+sum[rs];	//回溯的时候更新每个节点的sum,因为子节点的值改变了;
}

int query(int x,int A,int B,int l,int r)//区间查询
{
	if(A<=l&&r<=B)
	{
		return sum[x];
	}
	pushdown(x,l,r);//在继续查询之前,先检查是否要下传标记
	int mid=(l+r)>>1,ans=0;
	if(A<=mid)	ans+=query(ls,A,B,l,mid);
	if(mid<B)	ans+=query(rs,A,B,mid+1,r);
	return ans;
}

例题:线段树单点修改–Hdu1754
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。
这让很多学生很反感。

不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
Input
本题目包含多组测试,请处理到文件结束。
在每个测试的第一行,有两个正整数 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。
Output
对于每一次询问操作,在一行里面输出最高成绩。
Sample Input
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
Sample Output
5
6
5
9

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//#define max(a,b) (a)>(b)?(a):(b);//自己宏定义会超时,原因百度见
const int max_=2e5+5;
char num[2];
int x,y,N,M,student[max_],Max[max_<<2];

void Build(int id,int l,int r)
{
	if(l==r)
	{
		Max[id]=student[l];
		return;	
	}
	int mid=(l+r)>>1;
	Build(id<<1,l,mid);
	Build(id<<1|1,mid+1,r);
	Max[id]=max(Max[id<<1],Max[id<<1|1]);
}

void update(int id,int pos,int l,int r,int d)
{
	if(l==r)
	{
		Max[id]=d;
		return;
	}
	int mid=(l+r)>>1; 
	if(pos<=mid)	update(id<<1,pos,l,mid,d);
	else 			update(id<<1|1,pos,mid+ 1,r,d);
	Max[id]=max(Max[id<<1],Max[id<<1|1]);
}

int query(int id,int x,int y,int l,int r) 
{
	if(x<=l&&r<=y)
	{
		return Max[id];
	}
	int mid=(l+r)>>1,ans=0;
	if(x<=mid)	ans=query(id<<1,x,y,l,mid);
	if(y>mid)	ans=max(ans,query(id<<1|1,x,y,mid+1,r));
	return ans;
}
int main()
{
	while(~scanf("%d%d",&N,&M))
	{
		for(int i=1;i<=N;i++)
		{
			scanf("%d",&student[i]);
		}
		Build(1,1,N);
		while(M--)
		{
			scanf("%s%d%d",num,&x,&y);
			if(num[0]=='Q')
			{
				printf("%d\n",query(1,x,y,1,N));
			}
			else
			{
				update(1,x,1,N,y);
			}
		}		
	}
	return 0;
}

例题:线段树区间修改–Loj1164

World is getting more evil and it’s getting tougher to get into the Evil League of Evil. Since the legendary Bad Horse has retired, now you have to correctly answer the evil questions of Dr. Horrible, who has a PhD in horribleness (but not in Computer Science). You are given an array of n elements, which are initially all 0. After that you will be given q commands. They are -

  1.  0 x y v - you have to add v to all numbers in the range of x to y (inclusive), where x and y are two indexes of the array.
    
  2.  1 x y - output a line containing a single integer which is the sum of all the array elements between x and y (inclusive).
    

The array is indexed from 0 to n - 1.

Input
Input starts with an integer T (≤ 5), denoting the number of test cases.

Each case contains two integers n (1 ≤ n ≤ 105) and q (1 ≤ q ≤ 50000). Each of the next q lines contains a task in one of the following form:

0 x y v (0 ≤ x ≤ y < n, 1 ≤ v ≤ 1000)

1 x y (0 ≤ x ≤ y < n)

Output
For each case, print the case number first. Then for each query ‘1 x y’, print the sum of all the array elements between x and y.

Sample Input
2

10 5

0 0 9 10

1 1 6

0 3 7 2

0 4 5 1

1 5 5

20 3

0 10 12 1

1 11 12

1 19 19

Sample Output
Case 1:

60

13

Case 2:

2

0

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
const  int N=1e5+5;
int T,n,m;
ll sum[N<<2];
int lazy[N<<2];
void pushdown(int id,int l,int r)
{
	if(lazy[id])
	{
		lazy[id<<1]+=lazy[id];
		lazy[id<<1|1]+=lazy[id];
		int mid=(l+r)>>1;
		sum[id<<1]+=(mid-l+1)*lazy[id];
		sum[id<<1|1]+=(r-mid)*lazy[id];
		lazy[id]=0;
	} 
}

void update(int id,int l,int r,int x,int y,int d)
{
	if(x<=l&&r<=y)
	{
		lazy[id]+=d;
		sum[id]+=(r-l+1)*d;
		return;
	}
	pushdown(id,l,r);
	int mid=(l+r)>>1;
	if(x<=mid)	update(id<<1,l,mid,x,y,d);
	if(y>mid)	update(id<<1|1,mid+1,r,x,y,d);
	sum[id]=sum[id<<1]+sum[id<<1|1];
}
ll query(int id,int l,int r,int x,int y)
{
	if(x<=l&&y>=r)
		return sum[id];
	pushdown(id,l,r);//检查下传 
	ll ans=0;
	int mid=(l+r)>>1;
	if(x<=mid)	ans+=query(id<<1,l,mid,x,y);
	if(y>mid)	ans+=query(id<<1|1,mid+1,r,x,y);
	return ans;
}

int main()
{
	scanf("%d",&T);
	int t=1;
	while(T--)
	{
		memset(sum,0,sizeof(sum));
		memset(lazy,0,sizeof(lazy));
		printf("Case %d:\n",t++);
		scanf("%d%d",&n,&m);
		int a,x,y,d;
		while(m--)
		{
			scanf("%d%d%d",&a,&x,&y);
			x++,y++;
			if(a==0)
			{
				scanf("%d",&d);
				update(1,1,n,x,y,d);	
			}
			else
			{
				cout<<query(1,1,n,x,y)<<endl;
			}	
		}		
	}  
	return 0;
 } 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值