POJ 2528 Mayor's posters(线段树+离散化)

59 篇文章 0 订阅

Description
那个城市里要竞选市长,然后在一块墙上可以贴海报为自己拉票,每个人可以贴连续的一块区域,后来帖的可以覆盖前面的,问到最后一共可以看到多少张海报(墙足够长可以满足所有海报的范围)
Input
第一行为用例组数T,每组用例第一行为人数n,之后n行每行两个整数表示此人贴海报范围
Output
对于每组用例,输出最后一共可以看到的海报数
Sample Input
1
5
1 4
2 6
8 10
3 4
7 10
Sample Output
4
Solution
这题单纯用线段树去求解一样不会AC,原因是建立一棵[1,10000000]的线段树,其根系是非常庞大的,TLE和MLE是铁定的了。所以必须离散化。通俗点说,离散化就是压缩区间,使原有的长区间映射到新的短区间,但是区间压缩前后的覆盖关系不变。举个例子:有一条1到10的数轴(长度为9),给定4个区间[2,4] [3,6] [8,10] [6,9],覆盖关系就是后者覆盖前者,每个区间染色依次为 1 2 3 4。
现在我们抽取这4个区间的8个端点,2 4 3 6 8 10 6 9
然后删除相同的端点,这里相同的端点为6,则剩下2 4 3 6 8 10 9
对其升序排序,得2 3 4 6 8 9 10
然后建立映射
2 3 4 6 8 9 10
↓ ↓ ↓ ↓ ↓ ↓ ↓
1 2 3 4 5 6 7
那么新的4个区间为 [1,3] [2,4] [5,7] [4,6],覆盖关系没有被改变。新数轴为1到7,即原数轴的长度从9压缩到6,显然构造[1,7]的线段树比构造[1,10]的线段树更省空间,搜索也更快,但是求解的结果却是一致的。
离散化时有一点必须要注意的,就是必须先剔除相同端点后再排序,这样可以减少参与排序元素的个数,节省时间,同时还要注意避免相同端点重复映射到不同的值,故新的离散方法为:在相差大于1的数间加一个数
Code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 100005
struct node
{
    int ll,rr,flag,n;//flag表示该节点是否被染色,n表示染的什么色 
}tree[8*maxn];
int s[4*maxn][2],t[4*maxn];//s记录各区间左右端点,t记录离散化之后的端点 
bool mark[4*maxn];//颜色标记数组 
int ans=0;
void build(int i,int l,int r)//建树 
{
    tree[i].ll=l;
    tree[i].rr=r;
    tree[i].flag=tree[i].n=0;//初始化 
    if(l<r)
    {
        int mid=(l+r)>>1;
        build(2*i,l,mid);
        build(2*i+1,mid+1,r);
    }
}
void update(int i,int l,int r,int k)
{
    if(tree[i].ll==l&&tree[i].rr==r)//刚好全覆盖 
    {
        tree[i].flag=1;//标记已被染色 
        tree[i].n=k;//染色 
        return ;
    }
    if(tree[i].flag==1)//如果当前线段已经有颜色,先将颜色复制给左右两个子树
    {
        tree[2*i].flag=1;
        tree[2*i+1].flag=1;
        tree[2*i].n=tree[i].n;
        tree[2*i+1].n=tree[i].n;
        tree[i].flag=2;
        tree[i].n=0;//标记线段没有颜色  
    }
    int mid=(tree[i].ll+tree[i].rr)>>1;
    if(r<=mid)//完全在左子树  
        update(2*i,l,r,k);
    else if(l>mid)//完全在右子树  
        update(2*i+1,l,r,k);
    else//两个子树都有  
    {
        update(2*i,l,mid,k);
        update(2*i+1,mid+1,r,k);
    }
    if(tree[2*i].flag==1&&tree[2*i+1].flag==1&&tree[2*i].n==tree[2*i+1].n)//两子树颜色相同则复制给上一个根节点 
    {
        tree[i].flag=1;
        tree[i].n=tree[2*i].n;
    }
    else//两子树颜色不同则标记该上一根节点有两种颜色 
        tree[i].flag=2;
}
void count(int i)//查询指定线段的颜色 
{
    if(tree[i].flag==1)//如果当前线段有颜色,记录,并且直接返回
    {
        mark[tree[i].n]=true;
        return ;
    }
    if(tree[i].ll==tree[i].rr)//遇到未被染色的叶子节点则返回 
        return ;
    if(tree[i].flag==0)//当前线段没有颜色 
        return ;
    //当前线段有多种颜色,递归左右子树 
    count(2*i);//递归左子树 
    count(2*i+1);//递归右子树 
}
int bound(int l,int r,int num)//二分搜索num所在节点 
{
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(t[mid]>num)
            r=mid-1;
        else
            l=mid+1;
    }
    return r;
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int m;
        cin>>m;
        int res=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&s[i][0],&s[i][1]);
            t[res++]=s[i][0];//提取所有区间的端点 
            t[res++]=s[i][1];
        }
        sort(t,t+res);//对端点排序 
        int res1=1;
        for(int i=1;i<res;i++)//删去重复端点 
            if(t[i]!=t[i-1])
                t[res1++]=t[i];
        for(int i=res1-1;i>0;i--)//避免相同的端点重复映射到不同的值
            if(t[i]!=t[i-1]+1)
                t[res1++]=t[i-1]+1;
        sort(t,t+res1);//对端点排序 
        for(int i=res1;i>0;i--)//端点数组下标变为从1开始 
            t[i]=t[i-1];
        build(1,1,res1+5);//建树 
        for(int i=1;i<=m;i++)//m张海报 
        {
            int tl=1,tr=res1;
            int temp1=bound(tl,tr,s[i-1][0]);//二分搜索左节点 
            int temp2=bound(tl,tr,s[i-1][1]);//二分搜索右节点 
            update(1,temp1,temp2,i);//对该海报所覆盖范围染色 
        }
        ans=0;//初始化 
        memset(mark,false,sizeof(mark));//初始化 
        count(1);//查询颜色 
        for(int i=1;i<=m;i++)//统计颜色种类 
            if(mark[i])
                ans++;
        cout<<ans<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值