zoukankan      html  css  js  c++  java
  • 线段树详解

    线段树及其应用

    线段树的几个基础操作:建树,单点查询,单点修改,区间查询,区间修改。其代码的主要思想为二分。参考博客:https://blog.csdn.net/qq_39826163/article/details/81436440

    数据结构:

    struct node
    {
        int l;                  //左端点
        int r;                  //右端点
        int sum;              	//区间和,因题目而异
        int f;            //懒标记
    }tree[4*maxn+1];
    

    1.建树

    建树的过程分为三步:1:给定左右端点的确定范围;2:如果是叶子结点,储存需要维护的信息;3:状态合并。下面是实现代码:

    void build(int l,int r,int cur)
    {
    	tree[cur].l = l,tree[cur].r = r;
    	if(tree[cur].l == tree[cur].r){
    		tree[cur].sum = arr[l];
    		return;
    	}
    	int mid = (l + r) >> 1;
    	build(l, mid, cur << 1);
    	build(mid + 1, r, cur << 1 | 1);
    	pushup(cur);					//状态合并
    }
    

    2.单点查询

    单点查询与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。下面是实现代码:

    int ask(int pos,int cur)            //cur为当前结点,x为待查位置
    {
        if(tree[cur].l==tree[cur].r)		return tree[k].value;
        int mid=(tree[cur].l+tree[cur].r)>>1;
        if(pos<=mid) ask(pos,cur<<1);       
        else ask(pos,cur<<1|1);				//递归左右孩子
    }
    

    3.单点修改

    和单点查询原理类似,结合建树过程,我们以增加某一个区间的长度代码为例,下面是实现代码:

    void modify(int pos,int x,int cur)				//在pos位置修改x(增加x),cur为当前结点编号
    {
    	if(tree[cur].l == tree[cur].r){
    		tree[cur].sum += x;
    		return;
    	}
        if(tree[cur].f)	pushdown(cur);
    	int mid = (tree[cur].l + tree[cur].r) >> 1;
    	if(pos <= mid)	modify(pos,x,cur<<1);
    	else         	modify(pos,x,cur<<1|1);
    	pushup(cur);
    }
    

    4.区间查询

    区间查询分为三种状态,1、当前结点区间的值全部为答案的一部分,;2、当前结点区间只有一部分是答案,3、当前结点区间包含了待查询的区间,根据x,y与mid的情况往下走。

    即mid=(l+r)/2

    y<=mid ,即 查询区间全在,当前区间的左子区间,往左孩子走;x>mid 即 查询区间全在,当前区间的右子区间,往右孩子走否则,两个子区间都走。

    下面是实现代码:

    void query(int l,int r,int cur)			  //l,r为待查询区间
    {
    	if(l <= tree[cur].l && tree[cur].r <=r ){
    		ans += tree[cur].sum;
    		return;
    	}
        if(tree[cur].f)	pushdown(cur);		//将更新信息传递给左右子树
    	int mid = (tree[cur].l + tree[cur].r) >> 1;
    	if(l <= mid) query(l,r,cur<<1);
    	if(mid < r)  query(l,r,cur<<1|1);
    }
    

    5.区间修改

    如果要修改一个区间的值,给一个区间内的每个数都加或减或修改时,如果我们只想查询某一个子区间的值,如修改[1,100]而只查询[1,2]的值,如果给所有区间都改得画,在树的深度很高的情况下会很浪费。所以这里引入了一个新状态——懒标记,其作用是存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。下面是懒标记的具体实现过程:

    懒标记下移:

    void pushdown(int cur)
    {
        tree[cur<<1].f+=tree[cur].f;
        tree[cur<<1|1].f+=tree[cur].f;
        tree[cur<<1].sum+=tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
        tree[cur<<1|1].sum+=tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
        tree[cur].f=0;
    }
    

    还有上面提到的pushup函数,我认为它和oushdown函数一起,是线段树的核心,其他的不过是模板而已,而这个是线段树真正灵活多变的地方,对于任意给定一个题目,你要依据题意,题目需要维护什么,你就维护什么,比如上面一直再说的oushup函数和这里的Pushdown,在这起到的是维护一个区间和的作用。不同题目真正不一样的代码应该就是这两个了。

    //以维护区间和为例:
    inline void pushup(int cur)
    {
    	tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
    }
    

    区间修改代码:

    void modify_interval(int l,int r,int x,int cur)     //[a,b]为待修改的区间,x为区间修改的值
    {
        if(tree[cur].l>=l&&tree[cur].r<=r)      //当前区间全部对要修改的区间有用 
        {
            tree[cur].value+=(tree[cur].r-tree[cur].l+1)*x;       //(r-1+1)区间点的总数
            tree[cur].f+=x;
            return;
        }
        if(tree[cur].f) 	pushdown(cur);                //懒标记下移
        int mid=(tree[cur].l+tree[cur].r)>>1;
        if(l<=mid) 		modify_interval(l,r,x,cur<<1);
        if(r>mid) 		modify_interval(l,r,x,cur<<1|1);
        pushup(cur);
    }
    

    例题AC代码:poj-2528:题意,每次在[l,r]区间贴广告,最多能看见多少个广告牌?

    思路:离散化+线段树区间染色

    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cstdlib>
    
    using namespace std;
    const int maxn=1e5+50;
    struct node
    {
    	int l,r,num;
    }tree[maxn<<2];
    int n,T,cnt,tot,li[maxn],ri[maxn];
    int point[maxn<<1];
    int ans;
    bool vis[maxn];
    inline void pushdown(int cur)
    {
    	tree[cur<<1].num=tree[cur].num;
    	tree[cur<<1|1].num=tree[cur].num;
    	tree[cur].num=0;
    }
    inline void build(int l,int r,int cur)		//initialization
    {
    	tree[cur].l=l,tree[cur].r=r;
    	if(l==r){
    		tree[cur].num=0;
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(l,mid,cur<<1);
    	build(mid+1,r,cur<<1|1);
    	tree[cur].num=0;
    }
    inline void modify(int l,int r,int x,int cur)
    {
    	if(tree[cur].l>=l&&tree[cur].r<=r){
    		tree[cur].num=x;
    		return;
    	}
    	if(tree[cur].num)	pushdown(cur);
    	int mid=(tree[cur].l+tree[cur].r)>>1;
    	if(l<=mid)	modify(l,r,x,cur<<1);
    	if(mid<r)	modify(l,r,x,cur<<1|1);
    }
    inline void query(int l,int r,int cur)
    {
    	if(tree[cur].num&&!vis[tree[cur].num]){
    		vis[tree[cur].num]=1;
    		ans++;
    		return;
    	}
    	if(l==r)	return;
    	if(tree[cur].num)	pushdown(cur);
    	int mid=(l+r)>>1;
    	query(l,mid,cur<<1);
    	query(mid+1,r,cur<<1|1);
    }
    
    int main()
    {
    	scanf("%d",&T);
    	while(T--)
    	{
    		memset(vis,false,sizeof(vis));
    		scanf("%d",&n);
    		cnt=ans=0;
    		for(int i=1;i<=n;++i)	scanf("%d %d",&li[i],&ri[i]);
    		for(int i=1;i<=n;++i){
    			point[++cnt]=li[i],	point[++cnt]=ri[i];
    		}
    		sort(point+1,point+cnt+1);
    		int now=unique(point+1,point+cnt+1)-(point+1);
    		tot=now;
    		for(int i=2;i<=now;++i){
    			if(point[i]-point[i-1]>1)
    				point[++tot]=point[i-1]+1;
    		}
    		sort(point+1,point+tot+1);
    		build(1,tot,1);
    		for(int i=1;i<=n;++i){
    			int l=lower_bound(point+1,point+tot+1,li[i])-point;
    			int r=lower_bound(point+1,point+tot+1,ri[i])-point;		//O(n*logn) algorithm
    			modify(l,r,i,1);
    		}
    		query(1,tot,1);
    		printf("%d
    ",ans);
    	}
    	system("pause");
    }
    

    POJ-3468 题意:裸线段树区间修改+区间查询,注意区间查询的时候别忘了pushdown就好了。

    #include<cstdio>
    #include<cstdlib>
    #include<iostream>
    
    using namespace std;
    const int maxn=1e5+50;
    typedef long long LL;
    int arr[maxn],n,q;
    LL ans;
    struct node
    {
    	int l,r,f;
    	LL sum;
    }tree[maxn<<2];
    inline void pushup(int cur)
    {
    	tree[cur].sum=tree[cur<<1].sum+tree[cur<<1|1].sum;
    }
    inline void build(int l,int r,int cur)
    {
    	tree[cur].l=l,tree[cur].r=r;
    	tree[cur].sum=tree[cur].f=0;
    	if(tree[cur].l==tree[cur].r){
    		tree[cur].sum=1LL*arr[l];
    		return;
    	}
    	int mid=(l+r)>>1;
    	build(l,mid,cur<<1);
    	build(mid+1,r,cur<<1|1);
    	pushup(cur);
    }
    inline void pushdown(int cur)
    {
    	tree[cur<<1].f+=tree[cur].f;
    	tree[cur<<1|1].f+=tree[cur].f;
    	tree[cur<<1].sum+=1LL*tree[cur].f*(tree[cur<<1].r-tree[cur<<1].l+1);
    	tree[cur<<1|1].sum+=1LL*tree[cur].f*(tree[cur<<1|1].r-tree[cur<<1|1].l+1);
    	tree[cur].f=0;
    }
    inline void query(int l,int r,int cur)
    {
    	if(l<=tree[cur].l&&tree[cur].r<=r){
    		ans+=tree[cur].sum;
    		return;
    	}
    	if(tree[cur].f)	pushdown(cur);
    	int mid=(tree[cur].l+tree[cur].r)>>1;
    	if(mid>=l)	query(l,r,cur<<1);
    	if(mid<r)	query(l,r,cur<<1|1);
    }
    inline void modify(int l,int r,int x,int cur)
    {
    	if(l<=tree[cur].l&&tree[cur].r<=r){
    		tree[cur].f+=x;
    		tree[cur].sum+=1LL*x*(tree[cur].r-tree[cur].l+1);
    		return;
    	}
    	if(tree[cur].f)	pushdown(cur);
    	int mid=(tree[cur].l+tree[cur].r)>>1;
    	if(mid>=l)	modify(l,r,x,cur<<1);
    	if(mid<r)	modify(l,r,x,cur<<1|1);
    	pushup(cur);
    }
    
    int main()
    {
    	scanf("%d%d",&n,&q);
    	for(int i=1;i<=n;++i)	scanf("%d",&arr[i]);
    	build(1,n,1);
    	while(q--)
    	{
    		char t;
    		getchar();
    		scanf("%c",&t);
    		if(t=='Q'){
    			ans=0;
    			int a,b;
    			scanf("%d %d",&a,&b);
    			query(a,b,1);
    			printf("%lld
    ",ans);
    		}
    		else{
    			int a,b,c;
    			scanf("%d %d %d",&a,&b,&c);
    			modify(a,b,c,1);
    		}
    	}
    	system("pause");
    }
    
    

    ps:pushdown可以写在函数最上方,玄学写法我也不懂,先pushdown,wa了可以试试qwq

  • 相关阅读:
    jQuery+ajax实现文件上传
    Jquery异步上传文件
    jQuery插件综合应用(三)发布文章页面
    jQuery插件综合应用(二)文字为主的页面
    网站开发常用jQuery插件总结(13)定位插件scrollto
    网站开发常用jQuery插件总结(12)固定元素插件scrolltofixed
    网站开发常用jQuery插件总结(11)折叠插件Akordeon
    网站开发常用jQuery插件总结(十)菜单插件superfish
    网站开发常用jQuery插件总结(九)侧边栏插件pageslide
    网站开发常用jQuery插件总结(八)标签编辑插件Tagit
  • 原文地址:https://www.cnblogs.com/StungYep/p/12254041.html
Copyright © 2011-2022 走看看