线段树是一种基础且强大的数据结构,修改与查询均为log2(n),是十分优秀的数据结构,下面展示了线段树的几种常见用法:
这个就不多说了,直接上代码
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(xtree[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(rtree[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>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>1;
return query(ls,l,mid,A,B)+query(rs,mid+1,r,A,B);
}
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
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=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=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
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<b||r>1;
insert(ls,L,mid,a,b,opt),insert(rs,mid+1,r,a,b,opt);
pushup(v);
// cout<
整体大同小异,主要说一下pushdown操作:
对于一个被完整包含的区间,每次修改的时候设置标记,我们并不将左右儿子也一起修改,而是直接返回,因为其实我们做了不必要的操作,也许根本不会用到我的后代,只有在需要使用后代的时候,我们才将信息传递下去。
原理就是对于一个区间[a,b],下面的所有结点[c,d],都有[c,d]属于[a,b],因此[a,b]状态的改变也就代表了[c,d]状态的改变。
看不懂也没关系啦,反正我也是co标之后隔了很长一段时间才理解的。
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
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].yb0)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~