线段树

一、基础知识

1.为什么叫线段树

定义
(图源:hznu_acm上课课件)

2.适用于什么问题

应用
(图源:hznu_acm上课课件)

3.区间结合律

  • 满足: 区间加法、区间乘法、区间最值、区间异或和、区间gcd(最大公因数)…
  • 不满足: 区间众数、取模运算、部分函数运算(eg.f(x)= 1 x \frac{1}{x} x1)…

线段树维护信息依赖于区间结合律

4.线段树是什么

3
(图源:hznu_acm上课课件)

5.基本思路

以区间和为例:

如何实现:push up
已知叶节点信息,通过区间结合律向上运算,求得根节点信息,即所有区间的总和

如何保存:开一个struct来表示节点node[n]

struct{
   int l,r,sum;   //l、r对应维护区间的左、右端点  //sum对应维护区间的区间和
}node[n];   //当前这个节点

对于根节点,下标为1
对于任意节点i,它的左儿子下标为i*2,它的右儿子下标为(i*2+1)
i*2  可以写成  i<<1
i*2+1  可以写成  i<<|1

线段树里面有build函数,用于建立一棵线段树。为了方便起见,我们建树时一般是从顶向下建的
1.先建左边
2.再建右边
3.push up(利用区间结合律)
注:(建左建右是一个递归结构,它的递归出口是:到叶节点就结束)

二、模板例题

单点修改 HDU - 1166
  • 题意: A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。Tidy要随时向Derek汇报某一段连续的工兵营地一共有多少人 ,例如Derek问:“Tidy,马上汇报第3个营地到第10个营地共有多少人!”Tidy就要马上开始计算这一段的总人数并汇报。但敌兵营地的人数经常变动,而Derek 每次询问的段都不一样 ,程序效率不够高的话,Tidy还是会受到Derek的责骂的。请你帮他设计程序。
  • 输入: 第一行一个整数T,表示有T组数据 。每组数据第一行一个正整数N(N<=50000),表示敌人有N个工兵营地 ,接下来有N个正整数,第i个正整数ai代表第i个工兵营地里开始时有ai个人 (1<=ai<=50)。
    接下来每行有一条命令,命令有4种形式:
    (1) Add i j,i和j为正整数,表示第i个营地增加 j个人(j不超过30)
    (2)Sub i j ,i和j为正整数,表示第i个营地减少 j个人(j不超过30);
    (3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个 营地的总人数;
    (4)End 表示结束,这条命令在每组数据最后出现;
    每组数据最多有40000条命令
  • 输出: 对第i组数据,首先输出“Case i:”和回车,对于每个Query询问,输出一个整数并回车,表示询问的段中的总人数,这个数保持在int以内。

Sample Input
1
10
1 2 3 4 5 6 7 8 9 10
Query 1 3
Add 3 6
Query 2 7
Sub 10 2
Add 6 3
Query 3 10
End

Sample Output
Case 1:
6
33
59

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring> 
using namespace std;
const int N=50010;
struct segmentTree{
	int l, r, sum;
}node[N<<2]; //开4倍
int a[N],kase=0;
void pushup(int i)
{
	node[i].sum=node[i<<1].sum+node[i<<1|1].sum; 
}
void build(int l,int r,int i)  //对[l,r]区间建立下标为i的线段树 
{
	node[i]={l,r};  //赋值  
	if(l == r)  //如果是叶节点 
	{
		node[i].sum=a[l];
		return;
	}
	int mid=l+r>>1;
	build(l,mid,i<<1);   //先建左边 
	build(mid+1,r,i<<1|1);   //再建右边 
	pushup(i);   //对i这个点进行左右合并 
 }
void modify(int p,int w,int i)  //p为要修改的点的下标 
{//从根开始,二分找到叶节点,直接将叶节点修改 //将 a[i]加上w 
	if(node[i].l==p&&node[i].r==p) //判断是否为叶节点 
	{
		node[i].sum+=w;
		return;
	}
	int mid=node[i].l+node[i].r>>1; //否则二分找叶节点 
	if(p<=mid)  modify(p,w,i<<1);  //如果修改的点在左边 则去左边看 
	else modify(p,w,i<<1|1);  //否则去右边看 
	pushup(i);  //因为上面if语句必有一句执行
	//也就是说i节点的子树必有一个被修改了,那么需要重新pushup计算区间和 
 } 
int query(int l,int r,int i)
{
	if(l<=node[i].l&&r>=node[i].r)
	   return node[i].sum;
	int mid=node[i].l+node[i].r>>1;
	int res=0;
	if(l<=mid) res+=query(l,r,i<<1);
	if(r>mid)  res+=query(l,r,i<<1|1);
	return res;
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		printf("Case %d:\n",++kase); 
		int n;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		   scanf("%d",&a[i]);
		build(1,n,1);  //对[1,n]区间建立下标为1的线段树(根节点) 
		char op[10];
		int x,y;
		while(scanf("%s",op))
		{
			if(!strcmp(op,"End")) break;
			if(!strcmp(op,"Add"))
			{
				scanf("%d%d",&x,&y);
				modify(x,y,1);  //从第一个开始递归 
			}
			else if(!strcmp(op,"Sub"))
			{
				scanf("%d%d",&x,&y);
				modify(x,-y,1);
			}
			else
			{
				scanf("%d%d",&x,&y);
		        printf("%d\n",query(x,y,1));
			}
		}
	}
	return 0;
}

(待更新…)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值