线段树笔记

b站复习:noip线段树与树状数组_哔哩哔哩_bilibili
问题一:

     有一个长度为 为n的序列,m次操作,将某个数修改为v,询问下标区间[l,r]的所有数之和?

l和r表示该节点代表的区间,v表示维护这个区间的信息,如区间和

struct node{
    int l,r,v;  
}sum[4*N];

注:一般要开到4N

更新信息:

void update(int x)
{
    sum[x]=sum[x<<1]+sum[x<<1+1];
}

建立线段树 (自顶向下) 

void build(int l,int r,int x)
{
    if(l==r){//叶子节点 
        sum[x]=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,x<<1);
    build(mid+1,r,x<<1+1);//表示左右子树的信息已经被更新好了 
    update(x); //更新信息 
 } 

查询

 int query(int A,int B,int l,int r,int x)
 {
     if(A<=l&&r<=B)//l到r完全落在要查询区间内
         return sum[x];
    int mid=(l+r)>>1,ans=0;
    if(A<=mid)
    ans+=query(A,B,l,mid,x<<1);
    if(mid<B)
    ans+=query(A,B,mid+1,r,x<<1+1);
    return ans;
  }

   单点修改

把pos这个位置节点值改成v,l,r,表示当前节点对应区间,x表示当前节点编号 
   

void change(int pos,int v,int l,int r,int x)
   {
        if(l==r)
        {
            sum[x]=v;
            return  ;
        }
        int mid=(l+r)>>1;
        if(pos<=mid) change(pos,v,l,mid,x<<1);
        else change(pos,v,mid+1,r,x<<1+1);
        update(x);
    } 

给定一个序列,求该序列逆序对的数量,其中0<=a[i]<=10^9

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=500010;
int a[N],b[N];
int sum[4*N];
void update(int x)
{
	sum[x]=sum[x*2]+sum[x*2+1];
}
void build(int l,int r,int x)
{
	if(l==r){
		sum[x]=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(l,mid,x*2);
	build(mid+1,r,x*2+1);
	update(x);
}
int query(int A,int B,int l,int r,int k)
{
	if(A<=l&&r<=B)  return sum[k];
	int mid=(l+r)>>1,ans=0;
	if(A<=mid) ans+=query(A,B,l,mid,k*2);
	if(mid<B) ans+=query(A,B,mid+1,r,k*2+1);
	update(k);
	return ans;
}
void change(int pos,int l,int r,int k)
{
	if(l==r)
	{
		sum[k]++;
		return ;
	}
	int mid=(l+r)>>1;
	if(pos>mid) change(pos,mid+1,r,k*2+1);
	else change(pos,l,mid,k*2);
	update(k);
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	//离散化
	int cnt=0;
	for(int i=1;i<=n;i++) 
	b[i]=a[i];
	sort(b+1,b+n+1);
	cnt=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++)
	a[i]=lower_bound(b+1,b+cnt+1,a[i]) -b;
	build(1,n,1);
	long long  ans=0;
	for(int i=1;i<=n;i++)
	{
		ans+=query(a[i]+1,n,1,n,1);
		change(a[i],1,n,1);
	}
	cout<<ans<<endl;
	return 0;
 } 

unique:将重复元素排在序列末尾,保证前面都是不重复元素,返回第一个不重复元素地址

lower_bound:返回序列中不小于a[i]的最小的元素的地址

例题

P3372 【模板】线段树 1 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ls (x<<1)
#define rs  (x<<1|1)
const int N=100010;

long long sum[4*N],tag[4*N],a[N];
void update(int x)
{
	sum[x]=sum[ls]+sum[rs];
}
void build(int l,int r,int x)
{
	if(l==r)
	{
	sum[x]=a[l];
	return ;	
	}
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	update(x);
}

void down(int l,int r,int x)
{
	int mid=(l+r)>>1;
	if(tag[x]!=0)
	{
	tag[ls]+=tag[x];
	tag[rs]+=tag[x];
	sum[ls]+=(mid-l+1)*tag[x];
	sum[rs]+=(r-mid)*tag[x];
	tag[x]=0;
	
	}
}
void add(int A,int B,int v,int l,int r,int x)
{
	if(A<=l&&r<=B)
	{
		tag[x]+=v;
		sum[x]+=v*(r-l+1);
		return ;
	}
	down(l,r,x);
	int mid=(l+r)>>1;
	if(A<=mid) add(A,B,v,l,mid,ls);
	if(mid<B) add(A,B,v,mid+1,r,rs);
	update(x);
}
long long query(int A,int B,int l,int r,int x)
{
	if(A<=l&&r<=B)
	return sum[x];
	down(l,r,x);
	long long  mid=(l+r)>>1;
	long long ans=0;
	if(A<=mid) ans+=query(A,B,l,mid,ls);
	if(mid<B) ans+=query(A,B,mid+1,r,rs);
	return ans; 
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	build(1,n,1);
	while(m--)
	{
		int t,x,y,k;
		scanf("%d",&t);
		if(t==1){
			scanf("%d%d%d",&x,&y,&k);
			add(x,y,k,1,n,1);
		}
		else{
			scanf("%d%d",&x,&y);
			cout<<query(x,y,1,n,1)<<endl; 
		}
	}
	return 0;
}

问题二:

有一个长度为 为n的序列,m次操作,将某个区间修改为v,询问下标区间[l,r]的所有数之和?


定义ls,rs分别为左右子树的编号 
 

#define ls(x<<1)
#define rs(x<<1|1)

更新信息
 

void update(int x)
{
    sum[x]=sum[ls]+sum[rs];
 } 


 
 
 标记下传

(需要的时候才修改)O(logn) 
 tag[x]不为0则表示以下节点需要修改 

 

 void down(int l,int r,int x)
 {
     int mid=(l+r)>>1;
     if(tag[x]>0){
         tag[ls]=tag[rs]=tag[x];
         sum[ls]=(mid-l+1)*tag[x];
         sum[rs]=(r-mid)*tag[x];
         tag[x]=0;//表示修改完成 
     }
  } 


  修改区间[A,B]为v 
 

 void change(int A,int B,int l,int r,int x,int v)
  {
      if(A<=l&&r<=B)
      {
          tag[x]=v;
          sum[x]=v*(r-l+1);
          return ;
      }
      down(l,r,x);//在修改前检查是否要下传标记 
      int mid=(l+r)>>1;
      if(A<=mid) change(A,B,v,l,mid,ls);
      if(mid<B) change(A,B,v,mid+1,r,rs);
      update(x);
  }


  查询[A,B]的区间和
 

 int query(int A,int B,int l,int r,int x)
  {
      if(A<=l&&r<=B)
          return sum[x];
          down(l,r,x);//在修改前检查是否要下传标记 
          int mid(l+r)>>1,ans=0;
          if(A<=mid) ans+=query(A,B,l,mid,ls);
          if(mid<B) ans+=query(A,B,mid+1,r,rs);
          //update(x);//下传标记不影响这里的值 
          return ans;
   } 

改编1(区间加)

有一个长度为 为n的序列,m次操作,将某个区间加上v,询问下标区间[l,r]的所有数之和?

只需修改两个函数:

void down(int l,int r,int x)
 {
 	int mid=(l+r)>>1;
 	if(tag[x]>0){
 		tag[ls]+=tag[x]; 
 		tag[rs]+=tag[x];
 		sum[ls]+=(mid-l+1)*tag[x];
 		sum[rs]+=(r-mid)*tag[x];
 		tag[x]=0;//表示修改完成 
	 }
  } 

区间[A,B]+v :

void change(int A,int B,int l,int r,int v,int x)
  {
  	if(A<=l&&r<=B)
  	{
  		tag[x]+=v;
  		sum[x]+=v*(r-l+1);
  		return ;
	  }
	  down(l,r,x);//在修改前检查是否要下传标记 
	  int mid=(l+r)>>1;
	  if(A<=mid) change(A,B,v,l,mid,ls);
	  if(mid<B) change(A,B,v,mid+1,r,rs);
	  update(x);
  }

 例题:​​​​​​P1908 逆序对 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)  

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define ls (x<<1)
#define rs  (x<<1|1)
const int N=100010;

long long sum[4*N],tag[4*N],a[N];
void update(int x)
{
	sum[x]=sum[ls]+sum[rs];
}
void build(int l,int r,int x)
{
	if(l==r)
	{
	sum[x]=a[l];
	return ;	
	}
	int mid=(l+r)>>1;
	build(l,mid,ls);
	build(mid+1,r,rs);
	update(x);
}

void down(int l,int r,int x)
{
	int mid=(l+r)>>1;
	if(tag[x]!=0)
	{
	tag[ls]+=tag[x];
	tag[rs]+=tag[x];
	sum[ls]+=(mid-l+1)*tag[x];
	sum[rs]+=(r-mid)*tag[x];
	tag[x]=0;
	
	}
}
void add(int A,int B,int v,int l,int r,int x)
{
	if(A<=l&&r<=B)
	{
		tag[x]+=v;
		sum[x]+=v*(r-l+1);
		return ;
	}
	down(l,r,x);
	int mid=(l+r)>>1;
	if(A<=mid) add(A,B,v,l,mid,ls);
	if(mid<B) add(A,B,v,mid+1,r,rs);
	update(x);
}
long long query(int A,int B,int l,int r,int x)
{
	if(A<=l&&r<=B)
	return sum[x];
	down(l,r,x);
	long long  mid=(l+r)>>1;
	long long ans=0;
	if(A<=mid) ans+=query(A,B,l,mid,ls);
	if(mid<B) ans+=query(A,B,mid+1,r,rs);
	return ans; 
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	build(1,n,1);
	while(m--)
	{
		int t,x,y,k;
		scanf("%d",&t);
		if(t==1){
			scanf("%d%d%d",&x,&y,&k);
			add(x,y,k,1,n,1);
		}
		else{
			scanf("%d%d",&x,&y);
			cout<<query(x,y,1,n,1)<<endl; 
		}
	}
	return 0;
}

改编2(区间加+查区间最小值)

有一个长度为 为n的序列,m次操作,将某个区间加上v,询问下标区间[l,r]的最小值

在改编一上再修改:

只需修改两个函数:

void update(int x)
{
	Min[x]=min(Min[ls]+Min[rs]);
 } 
void down(int l,int r,int x)
 {
 	int mid=(l+r)>>1;
 	if(tag[x]>0){
 		tag[ls]+=tag[x]; 
 		tag[rs]+=tag[x];
 		Min[ls]+=tag[x];
 		Min[rs]+=tag[x];
 		tag[x]=0;//表示修改完成 
	 }
  } 

​
void change(int A,int B,int l,int r,int x)
  {
  	if(A<=l&&r<=B)
  	{
  		tag[x]+=v;
  		Min[x]+=v;
  		return ;
	  }
	  down(l,r,x);//在修改前检查是否要下传标记 
	  int mid=(l+r)>>1;
	  if(A<=mid) change(A,B,v,l,mid,ls);
	  if(mid<B) change(A,B,v,mid+1,r,rs);
	  update(x);
  }

​

查询的时候ans=0x3f3f3f3f

改编3(区间加+查区间最小值个数)

有一个长度为 为n的序列,m次操作,将某个区间加上v,询问下标区间[l,r]的最小值的个数

更新信息


void update(int x)
{
	Min[x]=min(Min[ls]+Min[rs]);
	if(Min[ls]==Min[rs]){
		Min[x]=Min[ls];
		cnt[x]=cnt[ls]+cnt[rs];
	}
	else{
		if(Min[ls]<Min[rs])
		Min[x]=Min[ls],cnt[x]=cnt[ls];
		else
		Min[x]=Min[rs],cnt[x]=cnt[rs]; 
		}
 } 

 //查询[A,B]的区间和
  pair<int,int> query(int A,int B,int l,int r,int x)
  {
  	if(A<=l&&r<=B)
  		return make_pair(Min[x],cnt[x]);
  		down(l,r,x);//在修改前检查是否要下传标记 
  		int mid(l+r)>>1;
	    ans=make_pair(0x3f3f3f3f,0);
  	     if(A<=mid)	ans=min(ans,query(A,B,l,mid,ls))
		if(mid<B) 
		{
		pair<int,int> tmp=min(ans,query(A,B,mid+1,r,rs));
  		if(tmp.first==ans.first) ans.second+=tmp.second;//最小值相等 
  		else if(tmp.first<ans.first) ans=tmp;
  	}
  		return ans;
   } 

make_pair:无需写出型别,直接生成pair对象

其余同改编2

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值