首页 > 其他 > 详细

线段树讲解

时间:2020-04-25 13:57:21      阅读:34      评论:0      收藏:0      [点我收藏+]

RMQ问题:区间最大值或者最小值问题,类似的还要区间和问题

操作:

(1)求最值 :区间内

(2)修改元素 :点修改

线段树:用于区间处理的数据结构,用二叉树构造

复杂度:O(nlogn):二叉折半查找

查找点或者区间的时候:顺着往下查找

修改元素:直接修改叶子节点,然后自底向上更新

!!!存储空间:4n

更新和查询:更新经过的每个节点,就把这个节点剩下的数量(长度)减1

复杂度:线段是把n个数按照二叉树进行分组,每次更新有关节点的时候,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数

例题:

还是last cows

第一种做法:用结构体实现线段树

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//线段树做法
/*
从后往前遍历输入的序列,遇到的每个值a表示此牛在剩余牛中排在第a+1个,删除此编号,循环此过程,最终得到的序列即为牛在此队列中的编号序列。
借助线段树查找未删除的数中排在第a+1个位置(编号排序位置)的牛的位置(读取顺序)
*/
struct node{
    int l,r,len;
}cow[100000];
int s[100000],ans[100000];
void build(int v,int l,int r){
    cow[v].l=l;
    cow[v].r=r;
    cow[v].len=r-l+1;
    if(l==r) return;
    int mid=(l+r)/2;
    build(v*2,l,mid);
    build(v*2+1,mid+1,r);
}
int que(int v,int k){
    --cow[v].len;
    if(cow[v].l==cow[v].r) return cow[v].r;
    //找到叶子节点, 注意此处不可用cow[v].len == 0代替,否则单支情况将直接返回,导致未达到最末端
    else if(cow[v*2].len>=k){
        return que(v*2,k);
    }
    else return que(v*2+1,k-cow[v*2].len);////!!!!
}
int main(){
    int n;
    while(~scanf("%d",&n)){
        for(int i=2;i<=n;i++) scanf("%d",&s[i]);
        s[1]=0;
        build(1,1,n);
        for(int i=n;i>=1;i--){
            ans[i]=que(1,s[i]+1);
        }
        for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    }
return 0;
}

第二种做法

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=11010;
const int INF=0x3fffffff;
typedef long long LL;
typedef unsigned long long ull;
//数组实现线段树
int n;
int pre[maxn],tree[maxn*4]={0},ans[maxn]={0};
void build(int n,int last_left){
	for(int i=last_left;i<last_left+n;i++) tree[i]=1; //最后一行赋值
	//从二叉树的最后一行倒推到根节点,根节点的值是牛的总数 
	while(last_left!=1){
		for(int i=last_left/2;i<last_left;i++) tree[i]=tree[i*2]+tree[i*2+1];
		last_left/=2;
	} 
}

int que(int u,int num,int last_left){  //查询+维护,求出当前区间中坐起第num个元素
	tree[u]--;
	if(tree[u]==0&&u>=last_left) return u;
	if(tree[u<<1]<num)   //左子区间数量不够,查到右子区间 
		return que((u<<1)+1,num-tree[u<<1],last_left); 
	if(tree[u<<1]>=num)  //左子区间数量够了 
		return que(u<<1,num,last_left);
}
int main(){
	int las;
	scanf("%d",&n);
	pre[1]=0;
	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
	las=1<<(int(log(n)/log(2))+1);
	//cout<<las<<endl;
	build(n,las);  //从后往前退出每次最后一个数字 
	for(int i=n;i>=1;i--) ans[i]=que(1,pre[i]+1,las)-las+1;
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]); 
return 0;
}

当数据太大:也可以考虑离散化,把原有的大二叉树压缩为小二叉树,但是压缩前后子区间的关系不变

区间修改

操作:(1)加 (2)查询和

lazy_tag方法:当修改一个整块区间时,只对这个线段区间进行整体上的修改,其内部每个元素内容先不修改,只有当这部分线段的一致性被破坏时才把变化之传给子区间(查询时也一样)

tag[]数组:记录节点i是否用到lazy原理,其值是op a b c中的c,如果做了多次lazy,那么add[]可以累加,如果在某次操作中被深入, 破坏了lazy,那么add[]归0

POJ 3468  A Simple Problem with Integers

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
//有错 
const int maxn=1e5+5;
const int INF=0x3fffffff;
typedef long long LL;
LL summ[maxn<<2],add[maxn<<2];  //4倍空间 
void pusup(int r){  //向上更新,把值给i递归到父节点  什么时候使用这个函数呢?当现在的节点发生变化,需要顺便改变父节点 
	summ[r]=summ[r<<1]+summ[r<<1|1];
}
void push_down(int rt,int m){ //更新r的子节点,m为长度 
 	if(add[rt]){
 		add[rt<<1]+=add[rt];
		add[rt<<1|1]+=add[rt];
		summ[rt<<1]+=(m-(m>>1))*add[rt];
		summ[rt<<1|1]+=(m>>1)*add[rt];
		add[rt]=0;  //取消本层的标记 
	 }
}
void build(int l,int r,int rt){  //用满二叉树建树 
	add[rt]=0;
	if(l==r) {
		scanf("%lld",&summ[rt]);   //叶子节点 赋值 
		return;
	}
	int mid=(l+r)/2;
	build(l,mid-1,rt<<1);
	build(mid+1,r,rt<<1|1);
	pusup(rt);  //这是个递归结构    向上更新区间和 
}
void update(int a,int b,LL c,int l,int r,int rt){  //区间更新,
	if(a<=l&&b>=r){   //lazy方法,包含在区间里面,就整体更新 
		summ[rt]+=(r-l+1)*c;
		add[rt]+=c;
		return;
	} 
	push_down(rt,r-l+1);  //向下更新
	int mid=(l+r)/2; 
	//这里就是在处理有分叉的情况 
	if(a<=mid) update(a,b,c,l,mid-1,rt<<1);  //分成两半,继续深入 
	if(b>mid) update(a,b,c,mid+1,r,rt<<1|1);
	pusup(rt);  //向上更新 
}

LL que(int a,int b,int l,int r,int rt){  //区间求和 
	if(a<=l&&b>=r) return summ[rt];  //满足lazy直接返回
	push_down(rt,r-l+1); //向下更新 
	int mid=(l+r)/2;
	LL ans=0;
	if(a<=mid) ans+=que(a,b,l,mid-1,rt<<1);
	if(b>mid) ans+=que(a,b,mid+1,r,rt<<1|1);
	return ans; 
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	build(1,n,1);  //先建树 
	
	while(m--){
		string str;
		int a,b;
		LL c;
		cin>>str;
		if(str[0]==‘C‘){
			scanf("%d %d %lld",&a,&b,&c);
			update(a,b,c,1,n,1);
		}
		else{
			scanf("%d %d",&a,&b);
			printf("%lld\n",que(a,b,1,n,1));
		}
	}
	
return 0;
}

  

 

线段树讲解

原文:https://www.cnblogs.com/shirlybaby/p/12772698.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!