zoukankan      html  css  js  c++  java
  • 线段树、树状数组

    RMQ问题:区间最大值或最小值

    操作:求区间最值、修改元素

    区间和问题:修改操作在求和

    线段树:用于区间处理的数据结构,二叉树构建,当查找点或者区间的时候,顺着节点往下找,最多log2n次就能找到,用了二叉树折半查找

    !修改和查询可以用一起做,所以复杂度是O(mlog2n),m次操作

    点修改:

    poj last cows

    方法1:暴力O(N^2)

    #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;
    //线段树
    //知道排在他前面的数比它小的有多少个
    int pre[8100];
    int ans[8100];
    int num[8100];
    //剩下的编号中pre[n]+1大的编号就是ans[n]
    //暴力:从pre的末尾开始算,没处理完一头牛,就需要重新排序,重新拍的时候可以做下一次查找,所以复杂度为O(n^2)
    int n; 
    int main(){
    	scanf("%d",&n);
    	pre[1]=0;
    	for(int i=1;i<=n;i++) num[i]=i;
    	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
    	for(int i=n;i>=1;i--){
    		int k=0;
    		for(int j=1;j<=n;j++){
    			if(num[j]!=-1) k++;
    			if(k==pre[i]+1){
    				ans[i]=num[j];
    				num[j]=-1;
    				break;
    			}
    		}
    	} 
    	for(int i=1;i<=n;i++) printf("%d
    ",ans[i]);
    return 0;
    }

      方法2:线段树

    #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=10000;
    const int INF=0x3fffffff;
    typedef long long LL;
    //存储空间:4*n
    //复杂度:
    //线段树吧n个数按照二叉树进行分组,每次更新有关节点时,这个节点下面的所有子节点都隐含被更新了,从而减少了操作次数
    struct node{
    	int l,r;
    	int len;
    }tree[4*maxn]; 
    void build(int left,int right,int y){
    	tree[y].l=left;
    	tree[y].r=right;
    	tree[y].len=right-left+1;
    	if(left==right) return;
    	build(left,(left+right)>>1,y<<1);  //左子树
    	build(((left+right)>>1)+1,right,(y<<1)+1); 
    }
    int que(int u,int num){  //查询+维护:所求为当前区间中坐起第num个元素 
    	tree[u].len--;  //对方问到的区间都进行处理 
    	//如果找到了(叶子)
    	if(tree[u].l==tree[u].r) return tree[u].l;
    	if(tree[u<<1].len<num) {
    		return que((u<<1)+1,num-tree[u<<1].len);  //左子区间个数不够 ,查询第右区间第 num-tree[u<<1].len个元素 
    	}
    	if(tree[u<<1].len>=num) return que(u<<1,num);  //左子区间够,就往左查 
    }
    int pre[maxn];
    int ans[maxn];
    int main(){
    	int n;
    	scanf("%d",&n);
    	pre[1]=0;
    	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
    	build(1,n,1);
    	for(int i=n;i>=1;i--){  //从后往前推出每次最后一位数字 
    		ans[i]=que(1,pre[i]+1);
    	}
    	for(int i=1;i<=n;i++){
    		printf("%d
    ",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=10000;
    const int INF=0x3fffffff;
    typedef long long LL;
    //用完全二叉树实现线段树
    int pre[maxn],ans[maxn],tree[maxn*4];//tree村的是数量 
    int n; 
    void build(int n,int last_left){
    	int i;
    	for(i=last_left;i<last_left+n;i++) tree[i]=1;
    	//从二叉树的最后一行倒推到根节点,根节点的值是牛的数量
    	while(last_left!=1){
    		for(i=last_left/2;i<last_left;i++) tree[i]=tree[i*2]+tree[i*2+1];
    		last_left=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) {  //左子区间个数不够了,查询右子区间坐起第num-tree[u<<1].len个元素  
    		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(){
    	scanf("%d",&n);
    	pre[1]=0;
    	for(int i=2;i<=n;i++) scanf("%d",&pre[i]);
    	int last_left=1<<(int(log(n)/log(2))+1);  //二叉树最后一行的最左边的一个,计算方法是找到离2最近的2的指数
    	build(n,last_left);
    	for(int i=n;i>=1;i--){
    		ans[i]=que(1,pre[i]+1,last_left)-last_left+1;
    	} 
    	for(int i=1;i<=n;i++) printf("%d
    ",ans[i]);
    return 0;
    }
    

      区间修改:

    加:把区间ai.....aj的值全部加上v

    查询:查询L,R之间所有的和

    lazy_tag方法:当修改的是一个整块的区间时,只对这个区间进行整体上的修改,其内部元素不需要改变,但是当这部分线段的一致性被破坏时,才会把变化值传递给子区间

    查询也是一样

    有错

    #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){
    	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){
    		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
    ",que(a,b,1,n,1));
    		}
    	}
    	
    return 0;
    }  

    树状数组BIT

    利用二进制特征进行检索的树状结构

    lowbit运算:找到x的二进制数的最后1个1

    #define lowbir(x) ((x)&-(-x))
    void add(int x,int d){  //更新数组tree[] 
    	while(x<=n){
    		tree[x]+=d;
    		x+=lowbit(x);
    	}
    } 
    //求和
    int summ(int x){
    	int ans=0;
    	while(x>0){
    		ans+=tree[x];
    		x-=lowbit(x);
    	}
    	return ans;
    } 
    

      tree[x]就是x前面lowbit(x)个数相加的结果

    再次计算2182题,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=10010;
    const int INF=0x3fffffff;
    typedef long long LL;
    #define lowbit(x) ((x)&(-x))
    int tree[maxn],pre[maxn],ans[maxn];
    int n;
    void add(int x,int d){  //更新数组tree[] 
    	while(x<=n){
    		tree[x]+=d;
    		x+=lowbit(x);
    	}
    } 
    //求和
    int summ(int x){
    	int ans=0;
    	while(x>0){
    		ans+=tree[x];
    		x-=lowbit(x);
    	}
    	return ans;
    } 
    int findpos(int x){   //寻找sum(x)=pre[i]+1所对应的x,就是第x头牛
    	int l=1,r=n;
    	while(l<r){
    		int mid=(l+r)>>1;
    		if(summ(mid)<x) l=mid+1;
    		else r=mid;
    	} 
    	return l;
    }
    int main(){
    	scanf("%d",&n);
    	pre[1]=0;
    	for(int i=2;i<=n;i++){
    		scanf("%d",&pre[i]);
    	} 
    	for(int i=1;i<=n;i++){
    		tree[i]=lowbit(i);  //这个题目特殊,不需要add初始化,直接用lowbit就可以了 
    	}
    	for(int i=n;i>=1;i--){
    		int x=findpos(pre[i]+1);
    		add(x,-1);  //更新tree数字,减少一个
    		ans[i]=x; 
    	} 
    	for(int i=1;i<=n;i++) printf("%d
    ",ans[i]);
    return 0;
    }
    

      

  • 相关阅读:
    xrange和range区别
    bool([x]) 将x转换为Boolean类型
    bin(x) 将整数x转换为二进制字符串
    chr(i) 返回整数i对应的ASCII字符
    音乐欣赏
    迅雷下载百度云引发的“事故”
    swift获取图片路径出错
    记号笔写在白板上引起的尴尬而又无奈的事件
    swift 3新特性总结
    watch
  • 原文地址:https://www.cnblogs.com/shirlybaby/p/12393272.html
Copyright © 2011-2022 走看看