题目:POJ - 2528
题意:n个人依次贴海报,给出每张海报所贴的范围li,ri 。求出最后还能看见多少张海报。
题解:因为范围过大,所以我们考虑离散化,离散化之后把数组的下标作为线段树建树的区间,但是要注意:
如下面的例子(题目的样例),因为单位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。
所以就需要每次在相差大于1的地方再加上一个数,这样才能保证答案的正确性,从而不让区间边界被修改时影响整体答案。
代码:
#include
#include
#include
#include
#define N 10005
using namespace std;
int t,n,ans,tot;
int a[4*N],li[N],ri[N],sum[N<<4];
bool vis[N<<4];
void pushdown(int node)
{
if(sum[node]!=-1)
{
sum[node<<1]=sum[node];
sum[node<<1|1]=sum[node];
sum[node]=-1;
}
}
void Update(int node,int x,int y,int l,int r,int z)
{
if(x<=l&&r<=y)
{
sum[node]=z;
return ;
}
pushdown(node);
int m=(l+r)>>1;
if(x<=m)Update(node<<1,x,y,l,m,z);
if(y>m)Update(node<<1|1,x,y,m+1,r,z);
}
void Query(int node,int l,int r)
{
if(sum[node]!=-1&&!vis[sum[node]])//这里只有pushdown操作,没有pushup
{
ans++;
vis[sum[node]]=1;
return ;
}
if(l==r)return ;
pushdown(node);
int m=(l+r)>>1;
Query(node<<1,l,m);
Query(node<<1|1,m+1,r);
}
int main()
{
scanf("%d",&t);
while(t--)
{
tot=0;
scanf("%d",&n);
memset(sum,-1,sizeof(sum));
for(int i=1;i<=n;i++)
{
scanf("%d%d",&li[i],&ri[i]);
a[++tot]=li[i];
a[++tot]=ri[i];
}
sort(a+1,a+tot+1);
int tmp=unique(a+1,a+tot+1)-(a+1);
tot=tmp;
for(int i=1;i<=tmp;i++)
if(a[i]-a[i-1]>1)
a[++tot]=a[i-1]+1;
sort(a+1,a+tot+1);
for(int i=1;i<=n;i++)
{
int x=lower_bound(a+1,a+tot+1,li[i])-a;
int y=lower_bound(a+1,a+tot+1,ri[i])-a;
Update(1,x,y,1,tot,i);//实际上是以下标为左右区间建树,而他们的值只是一个判断的标准,这样来实现
}
memset(vis,0,sizeof(vis));
ans=0;
Query(1,1,tot);
printf("%d\n",ans);
}
return 0;
}