题目大意
阿布扎比(Bytetown)的公民无法忍受,市长竞选活动的候选人一直在各地兴高采烈地张贴选举海报。 市议会最终决定建造一个选举墙来放置海报,并引入以下规则:
每个候选人都可以在墙上贴一张海报。
所有海报的高度等于墙的高度; 海报的宽度可以是任意整数个字节(byte是字节镇的长度单位)。
墙分为多个部分,每个部分的宽度为一个字节。
每个海报必须完全覆盖连续多个墙段。
他们建造了一堵10000000字节长的墙(以便有足够的空间容纳所有候选人)。 当竞选活动重新开始时,候选人将他们的海报放在墙上,并且海报的宽度差别很大。 此外,候选人开始将他们的海报放置在其他海报已经占据的墙段上。 Bytetown的每个人都很好奇,他们的海报在选举前的最后一天(全部或部分)可见。
您的任务是找到所有张贴者的可见张贴者的数目,并给出有关张贴者的尺寸,
思路分析
拿到题,看到这个区间覆盖,先是想到了线段树,有个基础的想法,对于每一个前来张贴的广告牌
,我们给区间中所有地方+1,最后的时候,对每一个广告牌区间查询
的最小值,如果是1,就说明至少有一处没被覆盖。however,范围太太太太大了。叶子一千万的树,我连建的勇气都没有。。。怎么办呢,超大范围专用药:离散化。
这道问题难就难在离散化上,ԾㅂԾ,,我们简单的离散化是这样的:离散化的简介
如下面的例子(题目的样例),因为单位1是一个单位长度,将下面的
1 2 3 4 6 7 8 10
— — — — — — — —
1 2 3 4 5 6 7 8
离散化 X[1] = 1; X[2] = 2; X[3] = 3; X[4] = 4; X[5] = 6; X[7] = 8; X[8] = 10
于是将一个很大的区间映射到一个较小的区间之中了,然后再对每一张海报依次更新在宽度为1~8的墙上(用线段树),最后统计不同颜色的段数。
但是只是这样简单的离散化是错误的,
如三张海报为:1~10 1~4 6~10
离散化时 X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 6, X[ 4 ] = 10
第一张海报时:墙的1~4被染为1;
第二张海报时:墙的1~2被染为2,3~4仍为1;
第三张海报时:墙的3~4被染为3,1~2仍为2。
最终,第一张海报就显示被完全覆盖了,于是输出2,但实际上明显不是这样,正确输出为3。
新的离散方法为:在相差大于1的数间加一个数,例如在上面1 4 6 10中间加5(算法中实际上1,4之间,6,10之间都新增了数的)
X[ 1 ] = 1, X[ 2 ] = 4, X[ 3 ] = 5, X[ 4 ] = 6, X[ 5 ] = 10
这样之后,第一次是1~5被染成1;第二次1~2被染成2;第三次4~5被染成3
最终,1~2为2,3为1,4~5为3,于是输出正确结果3
好了知道怎么离散化,下来就是线段树的问题了,线段树教程+模板。这道题里我们显然不需要build操作,但是需要区间修改和区间查询。对数数组 ,我们根据输入的顺序不断的操作输入区间 ,给这个区间附上标记 ,所有update函数变成了,
void pushdown(int rt)
{
sum[rt<<1]=sum[rt];
sum[rt<<1|1]=sum[rt];
sum[rt]=-1;
}
void update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
sum[rt]=C;
return ;
}
if(sum[rt]!=-1)
pushdown(rt);
int m=(l+r)>>1;
if(m>=R) update(L,R,C,l,m,rt<<1);
else if(L>m) update(L,R,C,m+1,r,rt<<1|1);
else update(L,m,C,l,m,rt<<1),update(m+1,R,C,m+1,r,rt<<1|1);
}
如果包含了整个区间,就给这个区间覆盖此海报的标记(无论之前有没有),如果需要分区间,首先要把当前节点已有的标记pushdown,传给下层节点,然后继续在下层节点update。
同样的查询也要做相应的修改
void query(int l,int r,int rt)
{
if(!vis[sum[rt]]&&sum[rt]!=-1)
{
ans++;
vis[sum[rt]]=1;
return ;
}
if(l==r)
{
return ;
}
if(sum[rt]!=-1)
pushdown(rt);
int m=(l+r)>>1;
query(l,m,rt<<1);
query(m+1,r,rt<<1|1);
}
如果一个节点被一个标记覆盖,说明这个标记是当前节点最后覆盖的海报,那么如果该海报还没有进行过统计,我们就ans++,否则二叉查询。
tips
- 注意pushDown的时候,一定是被标记过的父节点才能pushdown,否则整棵树都会被push成0。
- 注意区间长度,不重复元素个数,tree的查询/更新范围之间的关系。
- 初始的root千万别是0
#include<math.h>
#include<string.h>
#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn=10005;
int n;
int vis[maxn<<3],sum[maxn<<4];
int li[maxn*2],ri[maxn*2],lsh[maxn<<2];
void pushdown(int rt)
{
sum[rt<<1]=sum[rt];
sum[rt<<1|1]=sum[rt];
sum[rt]=-1;
}
void update(int L,int R,int C,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
sum[rt]=C;
return ;
}
if(sum[rt]!=-1)
pushdown(rt);
int m=(l+r)>>1;
if(m>=R) update(L,R,C,l,m,rt<<1);
else if(L>m) update(L,R,C,m+1,r,rt<<1|1);
else update(L,m,C,l,m,rt<<1),update(m+1,R,C,m+1,r,rt<<1|1);
}
int ans;
void query(int l,int r,int rt)
{
if(!vis[sum[rt]]&&sum[rt]!=-1)
{
ans++;
vis[sum[rt]]=1;
return ;
}
if(l==r)
{
return ;
}
if(sum[rt]!=-1)
pushdown(rt);
int m=(l+r)>>1;
query(l,m,rt<<1);
query(m+1,r,rt<<1|1);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(sum,-1,sizeof(sum));
memset(vis,0,sizeof(vis));
int tot=0;
for(int i=0;i<n;i++)
{
scanf("%d%d",&li[i],&ri[i]);
lsh[tot++]=li[i];
lsh[tot++]=ri[i];
}
sort(lsh,lsh+tot);
int mm=unique(lsh,lsh+tot)-lsh;
int tt=mm;
for(int i=1;i<tt;i++)
{
if(lsh[i]-lsh[i-1]>1)
lsh[mm++]=lsh[i-1]+1;
}
sort(lsh,lsh+mm);
for(int i=0;i<n;i++)
{
int x=lower_bound(lsh,lsh+mm,li[i])-lsh;
int y=lower_bound(lsh,lsh+mm,ri[i])-lsh;
update(x,y,i,0,mm-1,1);
}
ans=0;
query(0,mm-1,1);
printf("%d\n",ans);
}
}