题目链接
题目大意
给定一条数轴,长度为 107 ,然后在数轴上的某些区间染色,第i次对区间染色为i,共染色n(n ≤ 105 )次。给出每次染色的区间,问最后能看见多少种颜色.
分析
这是我写的第一道离散化的题啊!虽然以前知道有这种技巧,但一直没有亲手写过题,通过这道题掌握了离散化的思想以及用STL的实现离散化的方法。
1.这道题为什么要离散化?
这道题数轴的长度范围为
107
,如果把所有单位长度都作为一个结点来建立线段树的话,空间和时间的复杂度都很大。
2.离散化的原理
首先举个栗子,给出两个需要覆盖的区间区间[1000,2000],[1990,2012] ,我们其实用不到[-∞,999][1001,1989][1991,1999][2001,2011][2013,+∞]这些值,只需要把1000、2000、1990、2012这四个端点值提取出来,排序,得到它们相对大小的映射,即:
1000–>1
1990–>2
2000–>3
2012–>4
那么原来的两个区间的映射为:
[1000,2000]–>[1,3]
[1990,2012]–>[2,4]
用映射后的区间解决原题,答案完全不受影响,但空间和时间复杂度大大减小了。
3.离散化的实现(STL写法)
我们把所有左右端点值存储在一个d[]数组,显然端点值有可能存在重复,因此需要先排序(用sort函数),再去重(用unique函数),这时d数组前涵盖所有端点值且无重复的那一段是一个严格单调递增的序列,接下来构建某个端点值的映射编号,只需在那个严格单调递增的序列中二分查找即可(用lower_bound函数)。
求端点值x的映射编号num的实现代码:
sort(d+1,d+cnt+1);//总共有cnt个端点坐标值
SIZE=unique(d+1,d+cnt+1)-d-1;//去重后的长度
num=lower_bound(d+1,d+SIZE+1,x)-d;//求出映射编号
注:
1.unique()函数返回元素去重后的尾地址,但由于STL中函数的区间都是左闭右开的,所以它的地址在不重复段的后一位。
2.unique()函数并不能真正地从数组中删除重复的元素,而是把重复的元素移到数组后面。即:d[1]~d[SIZE]为所求的严格单调递增序列,d[SIZE+1]~d[cnt]为重复的元素。
3.unique()函数使用前需要先将数组排序。若想对结构体数组去重需要重载“==”运算符。
总结一下数据离散化的过程:排序、去重、映射。
**************************************************分割线*****************************************************
好了解决完离散化的问题,接下来我们只需要解决n个映射后的新区间的覆盖问题。
不重复的端点有SIZE个,我们就建立一棵有SIZE个叶子节点的线段树Tree[]来记录颜色信息,初始值为0,第i次覆盖就把相应区间颜色值更新为i,最后只需看叶子节点有几种颜色值即可。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int MAXN=10010;
int Tree[MAXN*8],lazy[MAXN*8],d[MAXN*2],SIZE;
//Tree[]用线段树维护颜色信息,lazy[]为懒惰标记,d[]为处理离散化的数组
void Build(int l,int r,int rt)//建树
{
if (l==r)
{
Tree[rt]=0;//颜色值初始为0
return;
}
int mid=(l+r)>>1;
Build(l,mid,ls);
Build(mid+1,r,rs);
}
void PushDown(int rt)//下推标记,可以延迟更新
{
if (lazy[rt])
{
lazy[ls]=lazy[rs]=lazy[rt];
Tree[ls]=lazy[rt];
Tree[rs]=lazy[rt];
lazy[rt]=0;//清空标记
}
}
void Update(int L,int R,int c,int l,int r,int rt)//[L,R]为更新区间,c为要覆盖的颜色值
{
if (L<=l&&r<=R)
{
Tree[rt]=c;
lazy[rt]=c; //延迟标记
return;
}
int mid=(l+r)>>1;
PushDown(rt);//要往子树访问时,下推标记
if (L<=mid) Update(L,R,c,l,mid,ls);
if (R>mid) Update(L,R,c,mid+1,r,rs);
}
int Query(int x,int l,int r,int rt)//单点查询叶子节点颜色值
{
if (l==r)
return Tree[rt];
int mid=(l+r)>>1;
PushDown(rt);
if (x<=mid) return(Query(x,l,mid,ls));
else if (x>mid) return(Query(x,mid+1,r,rs));
}
void Count()//统计叶子节点有多少个颜色值
{
int book[MAXN*2],ans=0,i;//book[i]用来标记i号颜色是否出现
memset(book,0,sizeof(book));
for (i=1;i<=SIZE;i++)
{
int t=Query(i,1,SIZE,1);
book[t]=1;
}
for (i=1;i<=SIZE;i++)
if (book[i]) ans++;
cout<<ans<<endl;
}
int main()
{
ios::sync_with_stdio(false);
int T,i,n,cnt,e[MAXN*2][3];
cin>>T;
while (T--)
{
cin>>n;
cnt=0;
/*离散化过程*/
for (i=1;i<=n;i++)
{
cin>>e[i][1]>>e[i][2];
d[++cnt]=e[i][1];
d[++cnt]=e[i][2];
}
sort(d+1,d+cnt+1);
SIZE=unique(d+1,d+cnt+1)-d-1;
memset(lazy,0,sizeof(lazy));
Build(1,SIZE,1);
for (i=1;i<=n;i++)//用映射后的区间处理问题
{
int L=lower_bound(d+1,d+SIZE+1,e[i][1])-d;
int R=lower_bound(d+1,d+SIZE+1,e[i][2])-d;
Update(L,R,i,1,SIZE,1);
}
Count();
}
return 0;
}
/*
1
5
1 4
2 6
8 10
3 4
7 10
*/