这里带大家简单了解一下C++中的线段树
一、何为线段树
线段树,顾名思义,就是一棵树上的每一个节点都表示的一段线段,例如:
或者
这就是两棵典型的线段树
二、为什么要用线段树
线段树上的每一条线段,都可以增加其他的东西(如权值等),可以动态地维护某些我们需要的信息,这使线段树拥有极大的灵活性,可以适应不同的需求
三、例题
一、
这道题,首先肯定可以用模拟来打代码,比如:
#include<cstdio>
#include<algorithm>
using namespace std;
inline void read(int &x) {
x=0;
int f=1;
char s=getchar();
while(s<'0'||s>'9') {
if(s=='-')
f=-1;
s=getchar();
}
while(s>='0'&&s<='9') {
x=x*10+s-48;
s=getchar();
}
x*=f;
}
inline void pr(int x) {
if(x<0) {
putchar('-');
x=-x;
}
if(x>9)
pr(x/10);
putchar(x%10+48);
}//快读快输不解释
struct node {
int l,r;
}a[100005];
int i,n,j,k,ans,L,R,s,e;
inline bool cmp(node a,node b) {
return a.l<b.l;
}
int main() {
read(L),read(R),read(n);
for(i=1;i<=n;i++)
read(a[i].l),read(a[i].r);
sort(a+1,a+1+n,cmp);//按左端排序
a[n+1].l=a[n+1].r=2147483647;//a[n+1]一定要定义为无限大,因为每一个区间要和后面的区间比较
//看是否重合,把a[n+1]定义为INT_MAX就可以避免
for(i=1;i<=n;i++) {
s=a[i].l;
e=a[i].r;
for(;a[i+1].l<=e;i++)//两区间相交
if(a[i+1].r>e)//取最大的右端点
e=a[i+1].r;//更新
ans+=e-s;//计算ans(左闭右开)
}
pr(ans);
}
这是一个很简单的模拟算法
但其实也可以用线段树来做
我们把整个墙的长度用区间来表示一下
然后用一棵二叉树来从L~R把每一个区间都二分出来
然后输入每一个区间时都有以下几种操作:
1、全覆盖这个区间,标记一下(cover=1)
2、不覆盖这个区间,return
3、覆盖一部分,左右两边dfs
当所有的线段都输完了之后,从树的根部往下搜,如果当前线段的cover==1,ans+=右端点-左端点(左闭右开)
节约时间,代码就留给大家自己去思考
二、
题目上是从后往前输入
这道题,理论上也是可以用模拟来做的,但这道题我打的是线段树的代码
模拟的话就是在每一次输入的时候把前面的更新一下,但时间复杂度可能比较高
而线段树在这里就比较方便了
我们把题目整理一下,变成了::
在一条线段上上色,每次上色会覆盖前面的颜色,求最后可以看到的颜色的个数
然后就可以做了
我们用一个变量c来存这条线段的颜色,第i个盒子的颜色就是i,底色是0
如果一条线段是混色就标记为-1
然后仿佛又重复上一道题的三个步骤
然后在统计就完事儿辣~\(≧▽≦)/~
代码还是很好懂,萌新在代码里面给大家解释::
#include<cstdio>
inline void read(int &x) {
x=0;
int f=1;
char s=getchar();
while(s<48||s>57) {
if(s=='-')
f=-1;
s=getchar();
}
while(s>47&&s<58) {
x=x*10+s-48;
s=getchar();
}
x=x*f;
}
inline void pr(int x) {
if(x>9)
pr(x/10);
putchar(x%10+48);
}//快读快输就算了吧
struct node {
int l,r,c;
}a[800005];
int i,n,j,k,m,L,R,ll,rr,o,ans;
bool flag[100005];
void build(int l,int r,int i) {
int mid=(l+r)/2;
a[i].l=l,a[i].r=r,a[i].c=0;
if(l==r)
return;
build(l,mid,i*2);
build(mid+1,r,i*2+1);
}//建树
void insert(int i,int l,int r,int color) {//插入一条线段
if(r<a[i].l||l>a[i].r) return;//全不覆盖
if(r>=a[i].r&&l<=a[i].l) {//全覆盖
a[i].c=color;
return;
}
if(a[i].c>=0) {//覆盖一部分
a[i*2].c=a[i*2+1].c=a[i].c;//子树继承父亲的颜色
a[i].c=-1;//当前为混色
}
insert(i*2,l,r,color);//没有全部覆盖或全覆盖就继续往两边添加
insert(i*2+1,l,r,color);
}
void find(int i) {//查找有多少个颜色
if(a[i].c==0) return;//底色
if(a[i].c>0) {//单一种颜色
if(flag[a[i].c]==0) {//没有出现过
ans++;//多一种
flag[a[i].c]=1;
}
return;
}
if(a[i].c==-1) {//混色
find(i*2);//继续往两边找
find(i*2+1);
}
}
int main() {
read(L),read(R),read(n);
o=-L;
L+=o,R+=o;//其实这里没有必要更新为正数,但变为正数后看着舒服些,调试也方便些
build(L,R,1);//建树
for(i=1;i<=n;i++) {
read(ll),read(rr);
ll+=o,rr+=o;
insert(1,ll,rr-1,i);//插入
}
find(1);
pr(ans);//找颜色
}
这就是第二题了
三、
我们把第二题改一下,如果线段的颜色可以相同那么最后会有多少种颜色呢?
这里有两种做法:
1、改变find算法,qiyu其余不变
2、增加一个ds表示当前线段被分成的段数,修改insert维护ds值, 降低查询时间
这里着重讲第二种(因为我做的第二种)
我就直接在代码里解释了::
#include<cstdio>
#include<iostream>
using namespace std;
inline void read(int &x) {
x=0;
int f=1;
char s=getchar();
while(s<48||s>57) {
if(s=='-')
f=-1;
s=getchar();
}
while(s>47&&s<58) {
x=x*10+s-48;
s=getchar();
}
x=x*f;
}
inline void pr(int x) {
if(x>9)
pr(x/10);
putchar(x%10+48);
}//快读快输不解释
struct node {
int l,r,c,ds/*段数*/,lc/*左端点颜色*/,rc/*右端点颜色*/;
}a[800005];
int i,n,j,k,m,L=2147483647,R=-2147483647,ll[100005],rr[100005],col[100005],o,ans;
bool flag[100005];
void build(int l,int r,int i) {//建树
int mid=(l+r)/2;
a[i].l=l,a[i].r=r,a[i].c=a[i].lc=a[i].rc=o;
if(l==r)
return;
build(l,mid,i*2);
build(mid+1,r,i*2+1);
}
void insert(int i,int l,int r,int color) {
if(r<a[i].l||l>a[i].r) return;//全不覆盖
if(r>=a[i].r&&l<=a[i].l) {//全覆盖
a[i].rc=a[i].lc=a[i].c=color;
a[i].ds=1;//都要更新
return;
}
if(a[i].c>0&&a[i].c==color) return;//为当前颜色,可以跳过了
if(a[i].c>0) {//颜色被改变,先传给子树
a[i*2].lc=a[i*2].rc=a[i*2].c=a[i].c;
a[i*2+1].lc=a[i*2+1].rc=a[i*2+1].c=a[i].c;
a[i*2].ds=a[i*2+1].ds=1;
a[i].c=-1;
}
insert(i*2,l,r,color);//左右两边插入
insert(i*2+1,l,r,color);
a[i].lc=a[i*2].lc;//往下传递各种信息
a[i].rc=a[i*2+1].rc;
a[i].ds=a[i*2].ds+a[i*2+1].ds;
if(a[i*2].rc==a[i*2+1].lc)a[i].ds--;
}
int main() {
read(o),read(n);
for(i=1;i<=n;i++)
read(ll[i]),read(rr[i]),read(col[i]),L=min(L,ll[i]),R=max(R,rr[i]);
L--,R++;//保险
int oo=-L;
L+=oo,R+=oo;
build(L,R,1);
for(i=1;i<=n;i++)
insert(1,ll[i]+oo,rr[i]-1+oo,col[i]);//每一段都插入
pr(a[1].ds);//直接输出第一段的颜色就好了
}
好了,今天的博客就到此为止吧,想了解更多,那就给个关注吧