UVA-10587 Mayor’s Posters
今天写了一下午才写出来的题,还参考了另一个博主
以及俺们俱乐部会长,郑(wo)光(lao)聪(po)的线段树模板
非常惭愧,甚是菜鸡,我写这篇博文就是为了加深自己的印象,如果读者不幸误入了这里,未免各位被我误导,请移步楼上两位的链接。
- 需要的知识点:
- 线段树区间更新
- 离散化
题面
The citizens of Bytetown, AB, could not stand that the candidates in the mayoral election campaign
have been placing their electoral posters at all places at their whim. The city council has finally decided
to build an electoral wall for placing the posters and introduce the following rules:
• Every candidate can place exactly one poster on the wall.
• All posters are of the same height equal to the height of the wall; the width of a poster can be
any integer number of bytes (byte is the unit of length in Bytetown).
• The wall is divided into segments and the width of each segment is one byte.
• Each poster must completely cover a contiguous number of wall segments.
They have built a wall 10000000 bytes long (such that there is enough place for all candidates).
When the electoral campaign was restarted, the candidates were placing their posters on the wall and
their posters differed widely in width. Moreover, the candidates started placing their posters on wall
segments already occupied by other posters. Everyone in Bytetown was curious whose posters will be
visible (entirely or in part) on the last day before elections.
Your task is to find the number of visible posters when all the posters are placed given the information
about posters’ size, their place and order of placement on the electoral wall.
Input
The first line of input contains a number c giving the number of cases that follow. The first line of
data for a single case contains number 1 ≤ n ≤ 10000. The subsequent n lines describe the posters in
the order in which they were placed. The i-th line among the n lines contains two integer numbers li
and ri which are the number of the wall segment occupied by the left end and the right end of the i-th
poster, respectively. We know that for each 1 ≤ i ≤ n, 1 ≤ li ≤ ri ≤ 10000000. After the i-th poster is
placed, it entirely covers all wall segments numbered li
, li+1, … , ri
.
Output
For each input data set print the number of visible posters after all the posters are placed.
要用的变量、常量、头文件
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
typedef long long LL;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
#define root 1,q,1
const LL N=1e5+2;
const LL inf=0x3f3f3f3f;
LL colour[4*N],newarr[4*N],lefta[4*N],righta[4*N],ans;
bool flag[4*N];
其中:
- colour数组存储节点的颜色(即最上方的海报)
- lefta、righta 数组存储读入数据的原始顺序(分别是左右端点)
- newarr 数组存储离散化后的区间端点
- flag 数组标记某种颜色是否出现(避免重复计数)
- lson、rson、root 是会(lao)长(po)的小聪明,少写一点参数。分别是左右儿子和根节点。
大致思路
贴海报,高度固定,横向被划分为字节,宽度字节数必须为整数,实际上就是线段区间,贴海报后面的覆盖前面的,问剩下有多少海报能被看到而不被覆盖。显而易见,大家第一时间想到的是线段树,直接套板子,但是由于这个数据范围很大,所以如果还按照经典的线段树写,肯定空间不够存,而且如果海报贴的很分散的话,空间也是一种浪费。所以这里我被会(lao)长(po)点醒,上网学习了一下线段树的离散化。
线段树区间更新
void PushDown(LL rt)
{
colour[rt<<1]=colour[rt];
colour[rt<<1 | 1]=colour[rt];
colour[rt]=-1;
}//向下传递
void interval_assign(LL L,LL R,LL val,LL l,LL r,LL rt)
{
if (L<=l && r<=R)
{
colour[rt]=val;
/*这里先不pushdown,在贴下一张海报时如果要经过会执行pushdown,否则如果遇到单值节点会停不下来,莫名多出俩儿子,然后该单节点被变回了-1,然后答案就少了。*/
return ;
}
if(colour[rt]!=-1)PushDown(rt);
/*如果这里是-1,那么可能已经贴过这里。如果已经pushdown过一次的话,再pushdown一次就是把儿子节点重新变成-1的空白状态,等于之前的染色白染了;就算没染过的话也没必要pushdown。因为这里的这个bug我wa了好几次......*/
LL m=(l+r)>>1;
if(L<=m) interval_assign(L,R,val,lson);
if(R>m) interval_assign(L,R,val,rson);
}
关键的一个函数就是PushDown函数,主要用于向下传递颜色,最终把所有的单点儿子染上相应的颜色。向下传递过后就把原本的父亲区间节点重新赋值为-1,以防止下一次贴海报的时候影响不同颜色的儿子节点的染色。
离散化
离散化主要包含两个步骤,一个是给原来的端点分配新的编号,另一个是按照端点查出新的编号以便编入线段树。
分配编号的步骤就这么几步:
1. 排序
2. 去重
3. 在表末插入空出的部分(即如果相邻节点原编号之差大于1的插入一个中间节点)
4. 最后再排序
LL discrete(LL n)
{
for(LL i=1; i<=n; i++)//和其他数组统一从1开始
{
cin>>newarr[(i<<1)-1]>>newarr[i<<1];
lefta[i]=newarr[(i<<1)-1];
righta[i]=newarr[i<<1];
}
sort(newarr,newarr+2*n+1);
LL m=1;
for(LL i=2; i<=2*n; i++)
{
if(newarr[i]!=newarr[i-1])
{
newarr[++m]=newarr[i];
}
}
for(LL i=m; i>=1; i--)//因为m要变大
{
if(newarr[i]-newarr[i-1]>1)
{
newarr[++m]=newarr[i]-1;
}
}
sort(newarr,newarr+m+1);
return m;
}
实际上按照会(lao)长(po)的办法:
1. 插入set
2. 插入空出的部分
两步就可以解决问题。。。
这里比较重要的是插入空出的部分,比如说我举这个例子:
1
3
1 10
1 4
6 10
执行去重、排序之后:1 4 6 10
然后写入线段树更新区间,你就发现答案只有两种颜色了,实际上4和6之间空出来的部分也就是5还是第一次贴海报留下的颜色,并没有被后面盖住。
也就是说,你需要而且只需要给它们之间留一个节点表示空出的部分,这一个节点可以表示两个节点之间的所有空出。
比如说我举的这个例子就是要变成1 4 5 6 10,答案才会对,但是就普遍性来说,应该变成这样:1 3 4 5 6 9 10
其实这里插入2、8而不插入3、9也行,只要是两段点中间的任意值就可以,代表这个区间的空出。
查找的话,如果是用了set,直接find就可以了,用数组的我,只能手写一个二分搜索……这里不再赘述。
几组比较容易错的数据,留待以后debug
Input
3
3
1 4
5 10
3 7
3
1 4
4 6
6 10
3
1 10
1 4
6 10
Output
3
3
3
完整代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
typedef long long LL;
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1 | 1
#define root 1,q,1
const LL N=1e5+2;
const LL inf=0x3f3f3f3f;
LL colour[4*N],newarr[4*N],lefta[4*N],righta[4*N],ans;
bool flag[4*N];
using namespace std;
/***********************************************************************************
向下传递
************************************************************************************/
void PushDown(LL rt,LL l,LL r)
{
colour[rt<<1]=colour[rt];
colour[rt<<1 | 1]=colour[rt];
colour[rt]=-1;
}
/***********************************************************************************
build(只是构建)
************************************************************************************/
void build(LL l,LL r,LL rt)
{
if (l==r)
{
colour[rt]=-1;
return ;
}
else
{
colour[rt]=-1;
LL m=(l+r)>>1;
build(lson); //构建左子树
build(rson); //构建右子树
}
}
/***********************************************************************************
区间赋值(l,r) = val
************************************************************************************/
void interval_assign(LL L,LL R,LL val,LL l,LL r,LL rt)
{
if (L<=l && r<=R)
{
colour[rt]=val;
return ;
}
if(colour[rt]!=-1)
{
PushDown(rt,l,r);//如果这里是-1,那么可能已经走过这里已经pushdown过一次的话,再pushdown等于白搞;没走过的话没必要pushdown
}
LL m=(l+r)>>1;
if(L<=m) interval_assign(L,R,val,lson);
if(R>m) interval_assign(L,R,val,rson);
}
/***********************************************************************************
查找
************************************************************************************/
void query(LL l,LL r,LL rt)
{
if(l==r)
{
if(colour[rt]>0&&!flag[colour[rt]])
{
ans++;
flag[colour[rt]]=true;
}
}
else
{
if(colour[rt]!=-1)
{
PushDown(rt,l,r);
}
LL m=(l+r)/2;
query(lson);
query(rson);
}
}
/***********************************************************************************
离散化
************************************************************************************/
LL discrete(LL n)
{
for(LL i=1; i<=n; i++)//和其他数组统一从1开始
{
cin>>newarr[(i<<1)-1]>>newarr[i<<1];
lefta[i]=newarr[(i<<1)-1];
righta[i]=newarr[i<<1];
}
sort(newarr,newarr+2*n+1);
LL m=1;
for(LL i=2; i<=2*n; i++)
{
if(newarr[i]!=newarr[i-1])
{
newarr[++m]=newarr[i];
}
}
for(LL i=m; i>=1; i--)//因为m要变大
{
if(newarr[i]-newarr[i-1]>1)
{
newarr[++m]=newarr[i]-1;
}
}
sort(newarr,newarr+m+1);
return m;
}
/***********************************************************************************
离散化的对应(二分搜索)
************************************************************************************/
LL searchnum(LL le,LL ri,LL na)
{
LL mid=(le+ri)>>1;
while(ri>le)
{
if(newarr[le]==na) return le;
else if(newarr[ri]==na) return ri;
else if(newarr[mid]==na)return mid;
else if(newarr[mid]<na)le=mid+1;
else ri=mid-1;
mid=(le+ri)>>1;
}
return -1;
}
/*************************************************************************************************************************
主程序
**************************************************************************************************************************/
void solve()
{
memset(flag,0,sizeof(flag));
LL n;
cin>>n;
LL q=discrete(n);
build(root);
for(LL i=1;i<=n;i++)
{
LL l=searchnum(1,q,lefta[i]);
LL r=searchnum(1,q,righta[i]);
interval_assign(l,r,i,root);
}
ans=0;
query(root);
cout<<ans<<"\n";
}
int main()
{
//freopen("input.txt","r",stdin);
//freopen("output.txt","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
LL T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}