线段树

线段树是一种基础且强大的数据结构,修改与查询均为log2(n),是十分优秀的数据结构,下面展示了线段树的几种常见用法:

1)单点修改:

这个就不多说了,直接上代码

struct Tree{
	int l,r;//区间
	int ls,rs;//左右儿子编号
	int sum;//需维护的答案,这里是维护区间和 
}; 
//结构体的定义可根据自身喜好决定是采用直接建树或者动态开点 
/***************************************************/
inline void pushup(int v){t[v].sum=t[v*2/ls].sum+t[v*2+1/rs].sum;} 
void build(int v,int l,int r){//直接建树 
	tree[v].a=l;tree[v].b=r;tree[v].c=0;//初始化,定界 
   	if(l==r)return;
   	int mid=(l+r)/2;
   	build(2*v,l,mid);
	build(2*v+1,mid+1,r);
}
void Add(int v,int x,int d){
	if(x<tree[v].a||x>tree[v].b)return;
   	if(x>=tree[v].a&&x<=tree[v].b)return tree[v].c=tree[v].c+d,void();//单点修改 
   	Add(2*v,x,d);
	Add(2*v+1,x,d);
	pushup(v);
}
void Ask(int v,int L,int r){
	if(r<tree[v].a||L>tree[v].b)return;
   	if(L<=tree[v].a&&r>=tree[v].b){Ans+=tree[v].c;return;}
   	Ask(2*v,L,r);
	Ask(2*v+1,L,r);
}
/***************************************************/
inline void insert(int &v,int l,int r,int k,int val){//这里是采用动态开点,与上来就建一整颗线段树相比,能节省(很多)空间 
	if(l>k||r<k)return ;
	if(!v)v=++cnt;
	if(l==k&&r==k)return t[v].sum+=val,void();
	int mid=l+r>>1;
	insert(ls,l,mid,k,val),insert(rs,mid+1,r,k,val);
	pushup(v);
}
inline int query(int v,int l,int r,int A,int B){
	if(l>B||r<A)return 0;
	if(A<=l&&r<=B)return t[v].sum;
	int mid=l+r>>1;
	return query(ls,l,mid,A,B)+query(rs,mid+1,r,A,B);
}

2)区间修改(下传标记优化):

Description

  在数轴上进行一系列操作。每次操作有两种类型,一种是在线段[a,b]上涂上颜色,另一种将[a,b]上的颜色擦去。问经过一系列的操作后,有多少条单位线段[k,k+1]被涂上了颜色。

Input

  第1行:2个整数n(0<=n<=60000)和m(1<=m<=60000)分别表示数轴长度和进行操作的次数。
  接下来m行,每行3个整数i,a,b, 0<=a<=b<=60000,若i=1表示给线段[a,b]上涂上颜色,若i=2表示将[a,b]上的颜色擦去。

Output

  文件输出仅有一行为1个整数,表示有多少条单位线段[k,k+1]被涂上了颜色。

Sample Input

10 5

1 2 8

2 3 6

1 1 10

2 4 7

1 1 5

Sample Output

7

 不难想到将整个将每次修改分为n次单点修改,但在一些动态操作下时间效率太低且不好实现,因此,对线段树的优化势在必行!!!

我们发现,一段区间的修改只会被自身和祖先的修改所影响,由此可以想到一个优化方法是:打标记。

何谓标记?对于本题而言,我们设置一个col标记表示是否被涂色(1为上色,-1为删除),那么本题的执行过程如下:

1)每次读入命令后,判断是修改(转到2)或是查询(转到4)

2)标记下传给左右儿子,然后继续递归直到完全包含,转到3

3)区间修改,修改后返回

4)标记下传给左右儿子,然后继续递归直到完全包含,转到5

5)区间统计,返回

还是通过代码更好的理解(代码下面有段解释,可以配合着看):

#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e5+10;
#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
#define REP(j,R,L) for(register int j=(R);j>=(L);--j)
struct linetree{int l,r,s,co;}a[maxn*4];
int n,m;
void build(int v,int l,int r){
	a[v].l=l;a[v].r=r;a[v].s=0;a[v].co=0;
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(2*v,l,mid);
	build(2*v+1,mid+1,r);
}
void color(int v,int l,int r){//添加 
	if(a[v].l>r||a[v].r<l)return;
	if(a[v].co==1)return;
	if(l<=a[v].l&&r>=a[v].r){a[v].s=a[v].r-a[v].l+1;a[v].co=1;return;}
	if(a[v].co==-1){
		a[(v<<1)].s=a[(v<<1)+1].s=0;
		a[(v<<1)].co=a[(v<<1)+1].co=-1;
		a[v].co=0;
	}
	color((v<<1),l,r);
	color((v<<1)+1,l,r);
	a[v].s=a[(v<<1)].s+a[(v<<1)+1].s;
}
void colo_(int v,int l,int r){//删除 
	if(a[v].l>r||a[v].r<l)return;
	if(a[v].co==-1)return;
	if(l<=a[v].l&&r>=a[v].r){a[v].s=0;a[v].co=-1;return;}
	if(a[v].co==1){
		a[(v<<1)].co=a[(v<<1)+1].co=1;
		a[(v<<1)].s=a[(v<<1)].r-a[(v<<1)].l+1;
		a[(v<<1)+1].s=a[(v<<1)+1].r-a[(v<<1)+1].l+1;
		a[v].co=0;
	}
	colo_((v<<1),l,r);
	colo_((v<<1)+1,l,r);
	a[v].s=a[(v<<1)].s+a[(v<<1)+1].s;
}
void solv(){
	n=get();m=get();
	build(1,1,n);
	int l,r,od;
	while(m--){
		od=get();l=get();r=get();--r;
		if(od==1)color(1,l,r);
		else colo_(1,l,r);
	}
	printf("%d\n",a[1].s);
}
int main(){
	solv();
	return 0;
}
/***********************************/
#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e5+10;
#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
#define REP(j,R,L) for(register int j=(R);j>=(L);--j)
struct Seg{
	int n,m,rt,cnt;
	int ls[N],rs[N],col[N],sum[N];//维护信息
	#define ls ls[v]
	#define rs rs[v]
	inline void pushup(int v){
		sum[v]=sum[ls]+sum[rs];
	} 
	inline void pushdn(int v,int L,int r){//下传信息
		if(!ls)ls=++cnt;//注意用动态开点的话标记也要开点 
		if(!rs)rs=++cnt;
		if(col[v]==1){
			int mid=L+r>>1;
			col[ls]=col[rs]=1;
			sum[ls]=mid-L+1;
			sum[rs]=r-mid;
			col[v]=0;
//			cout<<L<<" "<<mid<<' '<<r<<" "<<mid-L+1<<" "<<sum[ls]<<"\n";
		}else if(col[v]==-1){
			col[ls]=col[rs]=-1;
			sum[ls]=0;
			sum[rs]=0;
			col[v]=0;
		}
	}
	inline void insert(int &v,int L,int r,int a,int b,bool opt){
		if(L>b||r<a)return ;
		if(!v)v=++cnt;//新建结点
//		cout<<v<<"\n";
		if(a<=L&&r<=b){//完全包含 
			if(opt)sum[v]=r-L+1,col[v]=1;
			else sum[v]=0,col[v]=-1;
			return ;
		}pushdn(v,L,r);
		int mid=L+r>>1;
		insert(ls,L,mid,a,b,opt),insert(rs,mid+1,r,a,b,opt);
		pushup(v);
//		cout<<ls<<" "<<rs<<" "<<"\n";
	}
	inline void solv(){
		n=get(),m=get();
		FOR(i,1,m){
			int opt=get(),a=get(),b=get()-1;
			insert(rt,1,n,a,b,bool(2-opt));//0删除,1添加 
//			cout<<"\n";
		}cout<<sum[rt]<<"\n";
	}
}t;
int main(){
	t.solv();
	return 0;
}

整体大同小异,主要说一下pushdown操作:

对于一个被完整包含的区间,每次修改的时候设置标记,我们并不将左右儿子也一起修改,而是直接返回,因为其实我们做了不必要的操作,也许根本不会用到我的后代,只有在需要使用后代的时候,我们才将信息传递下去。

 

原理就是对于一个区间[a,b],下面的所有结点[c,d],都有[c,d]属于[a,b],因此[a,b]状态的改变也就代表了[c,d]状态的改变。

看不懂也没关系啦,反正我也是co标之后隔了很长一段时间才理解的。

 

3)扫描线(USACO3.1.4):

Description

N 个不同的颜色的不透明的长方形 ( 1 <= N <= 100 ) 被放置在一张宽为:A、长为:B 的白纸上。这些长方形被放置时,保证了它们的边于白纸的边缘平行。所有的长方形都放置在白纸内,所以我们会看到不同形状的各种颜色。坐标系统的原点 ( 0,0 ) 设在这张白纸的左下角,而坐标轴则平行于边缘。

Input

  按顺序输入放置长方形的方法。第一行输入的是那个放在底的长方形(即白纸)。
  第 1 行: A ,B 和 N,其中 A 和 B 表示白纸宽和长(1 <=A, B<=10,000),N 表示有 N 张不同颜色的不透明长方形; 
  接下来 N 行: 每行有五个整数:lx, ly, rx, ry, color,表示一个颜色不透明长方形放置在白纸上的左下角坐标,右上角坐标和颜色。其中颜色 1 和底部白纸的颜色相同。颜色 1和底部白纸的颜色相同。 (1 <= color<= 2500)

Output

  包含一个所有能被看到颜色连同该颜色的总面积的清单( 即使颜色的区域不是连续的),按color的增序顺序。 
  不要显示没有区域的颜色。

Sample Input

20 20 3

2 2 18 18 2

0 8 19 19 3

8 0 10 19 4

Sample Output

1 91

2 84

3 187

4 38

什么叫做扫描线呢?想象一根很长的平行y轴的线(x轴也可,看个人习惯),我们将这条线从左往右扫描,遇到一个“障碍”便停下计算更新答案。

#include<bits/stdc++.h>
using namespace std;
inline int get(){register int re=0,f=1;register char c;while(c=getchar(),(c>='0'&&c<='9')^1)f=c^'-';while(re=(re<<1)+(re<<3)+(c^48),c=getchar(),(c>='0'&&c<='9'));return f?re:-re;}
const int N = 1e5+10;
#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
#define REP(j,R,L) for(register int j=(R);j>=(L);--j)
struct doc{
    int xa,ya,xb,yb,c;
}a[N];
struct Tree{
    int L,r,col;
};
int xl[N],qx[N];//离散化
int ans[N];//记录答案
struct segment{
    Tree t[N];
    int n;
    inline void Find(int v,int i){
        if(!t[v].col)return;//子树及自身均未涂色
        if(t[v].col>0){
            ans[t[v].col]+=(t[v].r-t[v].L+1)*(qx[i+1]-qx[i]);
            t[v].col=0;
            return;
        }
        Find(v<<1,i),Find(v<<1|1,i);
        t[v].col=0;//注意此处应该为0,为下一个区间作准备
    }
    inline void insert(int v,int idx){
        if(a[idx].ya>t[v].r||a[idx].yb<t[v].L)return;//不相交
        if(t[v].col>0)return;//已经涂色,则返回
        if(a[idx].ya<=t[v].L&&t[v].r<=a[idx].yb&&t[v].col!=-1)return t[v].col=a[idx].c,void();//完全覆盖且子树中没有涂色结点
        insert(v<<1,idx),insert(v<<1|1,idx);
        t[v].col=-1;//-1表示它的子树中已经有涂色的
    }
    inline void build(int v,int L,int r){
        t[v]=(Tree){L,r,0};
        if(L==r)return ;
        int Mid=L+r>>1;
        build(v<<1,L,Mid),build(v<<1|1,Mid+1,r);
    }
    inline void init(int _n){
        build(1,0,n=_n);
    }
}tr;
struct Seg{
    int X,Y,n;
    //doc a[N];存储矩形记录
    inline void print(){
        ans[1]=X*Y;
        FOR(i,2,2500)ans[1]-=ans[i];
        FOR(i,1,2500)if(ans[i])printf("%d %d\n",i,ans[i]);
    }
    inline void solv(){
        tr.init(Y);//初始化
        FOR(i,1,qx[0]-1){
            REP(j,n,1)if(a[j].xa<=qx[i]&&a[j].xb>qx[i])tr.insert(1,j);
            tr.Find(1,i);
        }
        print();
    }
    inline void init(){
        X=get(),Y=get(),n=get();
        FOR(i,1,n){
            int xa=get(),ya=get(),xb=get(),yb=get()-1,col=get();
            a[i]=(doc){xa,ya,xb,yb,col};//注意线段树按点权建
            xl[xa]=xl[xb]=1;
        }FOR(i,0,X)if(xl[i])qx[++qx[0]]=i;
    }
}z;
int main(){
    z.init();
    z.solv();
    return 0;
}

持续更新ing~

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值