线段树基础

官方介绍:

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点,对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

我对线段树的理解差不多就是有那么一个区间,一直二分到不能分,也就是下传到每一个元素本身,比如在上图的[1,10]区间我寻找3,这显然是二分搜索,如果我要给3加上5呢  那么显然不仅3本身要变,包含3的区间都需要加5。这种线段树的方法就大大降低了时间复杂度。而线段树的的区间操作分为两种:单点更新和区间更新。

 

 

 

单点更新:

 

 

代码主要实现两个功能:1修改、2查询。

 

修改:

 

void modify(int p,int l,int r,int x,int v)
{
    a[p]+=v;
    if(l==r){
 return ;
 }
    int mid=(l+r)/2;
    if(mid>=x)modify(p*2,l,mid,x,v);
    else modify(p*2+1,mid+1,r,x,v);
}

 

这是一种写法,也可以用push_up将子结点的信息更新到父亲节点,代码如下 :

 

void up(int p)
{
 a[p]=a[p*2]+a[p*2+1];
}
void modify(int p,int l,int r,int x,int v)
{
    if(l==r){
     a[p]+=v;
 return ;
 }
    int mid=(l+r)/2;
    if(mid>=x)modify(p*2,l,mid,x,v);
    else modify(p*2+1,mid+1,r,x,v);
    up(p);
}

 

 

原先我更偏向第一种写法,但是后来我接触到后面的区间更新之后就改成第二种了,更形象,好理解些。

我也再次注释一下这些参数的意义,以备复习:p:当前的结点、l,r:所查询的区间的起点与终点,会更新、x,v:因为是修改自然少不了要修改的值和修改内容啦,x即为你要改的数,而v就是你要对x的操作内容,默认+。

 

 

 

查询:

 

 

int query(int p,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)return a[p];//所查询区间为整个区间
    int mid=(l+r)/2,res=0;
    if(x<=mid) res+=query(p*2,l,mid,x,y);
    if(y>mid) res+=query(p*2+1,mid+1,r,x,y);
    return res;
}

没什么好说的,就是X,Y 为所查询区间,最后返回的res为所查询区间的内容——这个区间内所有元素之和。

 

 

基础知识介绍到这里  来一题模板题:

 

 

 

 

问题描述

有一种神奇斑点蛇,蛇如其名,全身都是斑点,斑点数量可以任意改变。
有一天,蒜头君十分的无聊,开始数蛇上的斑点。假设这条蛇的长度是N cm,蒜头君已经数完开始时蛇身的第i上有ai个斑点。
现在蒜头君想知道这条斑点蛇的任意区间的蛇身上一共有多少个斑点。这好像是一个很容易的
事情,但是这条蛇好像是和蒜头君过不去,总是刻意的改变蛇身上的斑点数量。
于是,蒜头君受不了了,加上蒜头君有密集型恐惧症。聪明又能干的你能帮帮他吗?
输入格式
第一行一个正整数 N(N≤50000)表示这条斑点蛇长度为 N 厘米,接下来有 N 个正整数,第 i 个正整数 ai​ 代表第 i 个斑点蛇第 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 厘米的斑点总数(包括第 i 厘米和第 j 厘米);
(4) End 表示结束,这条命令在每组数据最后出现;
最多有 40000 条命令。
输出格式
对于每个 Query 询问,输出一个整数并回车,表示询问的段中的总斑点数,这个数保证在int范围内。
样例输入
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
样例输出
6
33
59

 

 

 

 

 

 

 

 

没什么好说的啊  模板题啊,直接把函数在上面一些就AC了呀

附上AC代码:

 

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn=50010;
int a[maxn*4+100];
int s[maxn*4+100];
void up(int p)
{
 a[p]=a[p*2]+a[p*2+1];
}
void modify(int p,int l,int r,int x,int v)
{
    if(l==r){
     a[p]+=v;
 return ;
 }
    int mid=(l+r)/2;
    if(mid>=x)modify(p*2,l,mid,x,v);
    else modify(p*2+1,mid+1,r,x,v);
    up(p);
}
int query(int p,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)return a[p];
    int mid=(l+r)/2,res=0;
    if(x<=mid) res+=query(p*2,l,mid,x,y);
    if(y>mid) res+=query(p*2+1,mid+1,r,x,y);
    return res;
}
int main()
{
    int n,d;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
       {
        scanf("%d",&d);
        modify(1,1,n,i,d);
    }
    string s;
   while(cin>>s){
    if(s=="End")break;
    int i,j;
    cin>>i>>j;
    if(s=="Query")    {
      int wyh=query(1,1,n,i,j);
      printf("%d\n",wyh);
    }
    if(s=="Add")    modify(1,1,n,i,j);
    if(s=="Sub")  modify(1,1,n,i,-j);
   }
  return 0;
}
 

值得一提的是:这线段树类的题目要把数组开到maxn的4倍以上。

在此再介绍一下区间更新的方法,以上是单点更新

 

区间更新:

介绍:lazy(懒惰)标记:

emmmmm其实我也不是很理解lazy标记的思想,但是差不多的意思是从原本的精确到点更新开始偷懒,比如1~10对3进行加3。原本单点更新需要进行到3本身,再上传区间和上来,但是我们仔细一想不对啊,他问的是区间和,假如我们所求的是[2,5],那其实无所谓2~5之间的数哪个数+3,反正对于区间都是一样的结果。所以我们可以直接对区间更新:

大体模板是:

void up(int p)
{
    s[p]=s[p*2]+s[p*2+1];
}
void down(int p,int l,int r)
{
    if(col[p])
    {
	int mid=(l+r)/2;
        s[p*2]+=col[p]*(mid-l+1);
        s[p*2+1]+=col[p]*(r-mid);
        col[p*2]+=col[p];
        col[p*2+1]+=col[p];
        col[p]=0;
    }
}
void modify(int p,int l,int r,int x,int y,int c){
    if(x<=l&&r<=y){
        s[p]+=(r-l+1)*c;
        col[p]+=c;
        return ;
    }
    down(p,l,r);
    int mid=(l+r)/2;
	if(x<=mid){
        modify(p*2,l,mid,x,y,c);
    }
    if(y>mid){
        modify(p*2+1,mid+1,r,x,y,c);
      }
    up(p);
}
int query(int p,int l,int r,int x,int y)
{
    if(x<=l&&r<=y)
    {
        return s[p];
    }
    down (p,l,r);
    int mid=(l+r)/2,res=0;
    if(x<=mid) {res+= query(p*2,l,mid,x,y);}
    if(y>mid){res+= query(p*2+1,mid+1,r,x,y);}
    return res;
}

在down函数中,什么时候需要+,什么时候不需要+:

s[p*2]+=col[p]*(mid-l+1);
s[p*2+1]+=col[p]*(r-mid);
col[p*2]+=col[p];
col[p*2+1]+=col[p];
col[p]=0;

以上代码是当区间内每一个值都需要进行操作时,而如果题目要求只需要对区间内某一个值操作时,以上代码加号统统不写

那就附上一题例题吧  也是模板题:

HDU 1698

In the game of DotA, Pudge’s meat hook is actually the most horrible thing for most of the heroes. The hook is made up of several consecutive metallic sticks which are of the same length.



Now Pudge wants to do some operations on the hook.

Let us number the consecutive metallic sticks of the hook from 1 to N. For each operation, Pudge can change the consecutive metallic sticks, numbered from X to Y, into cupreous sticks, silver sticks or golden sticks.
The total value of the hook is calculated as the sum of values of N metallic sticks. More precisely, the value for each kind of stick is calculated as follows:

For each cupreous stick, the value is 1.
For each silver stick, the value is 2.
For each golden stick, the value is 3.

Pudge wants to know the total value of the hook after performing the operations.
You may consider the original hook is made up of cupreous sticks.

Input

The input consists of several test cases. The first line of the input is the number of the cases. There are no more than 10 cases.
For each case, the first line contains an integer N, 1<=N<=100,000, which is the number of the sticks of Pudge’s meat hook and the second line contains an integer Q, 0<=Q<=100,000, which is the number of the operations.
Next Q lines, each line contains three integers X, Y, 1<=X<=Y<=N, Z, 1<=Z<=3, which defines an operation: change the sticks numbered from X to Y into the metal kind Z, where Z=1 represents the cupreous kind, Z=2 represents the silver kind and Z=3 represents the golden kind.

Output

For each case, print a number in a line representing the total value of the hook after the operations. Use the format in the example.

Sample Input

1
10
2
1 5 2
5 9 3

Sample Output

Case 1: The total value of the hook is 24.

不多bb附上AC CODE 

#include <iostream>
#include <cstdio>
#include <cstring>
#define maxn 100050
using namespace std;
int s[maxn*4];
int col[4*maxn];
void up(int p)
{
	s[p]=s[2*p]+s[2*p+1];
}
void down(int p,int l,int r)
{
	if(col[p])
	{
		int mid=(l+r)/2;
		s[p*2]=col[p]*(mid-l+1);
		s[2*p+1]=col[p]*(r-mid);
		col[2*p]=col[p];
		col[2*p+1]=col[p];
		col[p]=0;
	}
}
void modify(int p,int l,int r,int x,int y,int c)
{
	if(x<=l&&y>=r)
	{
		s[p]=c*(r-l+1);
		col[p]=c;
		return ;
	}
	down(p,l,r);
	int mid=(l+r)/2;
	if(mid>=x)
	modify(p*2,l,mid,x,y,c);
	if(y>mid)
	modify(p*2+1,mid+1,r,x,y,c);
	up(p);
}
int query(int p,int l,int r,int x,int y)
{
	if(x<=l&&y>=r)return s[p];
	down(p,l,r);
	int mid=(l+r)/2,res=0;
	if(x<=mid) res+=query(p*2,l,mid,x,y);
	if(y>mid)  res+=query(p*2+1,mid+1,r,x,y);
	return res;
}
int main()
{
	int t;
	int ans;
	ans=0;
	scanf("%d",&t);
	while(t--)
	{
	ans++;
	int n,q;
	scanf("%d%d",&n,&q);
	memset(col,0,sizeof(col));
	modify(1,1,n,1,n,1);
	while(q--)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		modify(1,1,n,x,y,z);
	}
	printf("Case %d: The total value of the hook is %d.\n",ans,query(1,1,n,1,n));
	//cout<<"The total value of the hook is "<<query(1,1,n,1,n)<<"."<<endl;
	for(int i=1;i<=maxn*4;i++)
		{
			s[i]=0;
			col[i]=0;
		}

}
	return 0;

}

其实这题我错了好多次  原本做过一次的  那个好做,这个变成循环输入后毛病就来了,我记得这个是因为我的初始化原本是1~n;问了学长才知道要初始化0到maxn*4;然后真的AC了  不过还有一题单点更新也司机吧鬼畜,等会我来写写  吃饭了(逃

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值