POJ 2528--Mayor's posters(线段树)

30 篇文章 0 订阅
24 篇文章 0 订阅

题意:给定一系列有先后顺序的数对[a,b],后面的数对可以覆盖前面的数对的部分或者全部,问最后可以看到多少个数对。

  1. 最简单的思路,建一hash数组,每输入一对[a,b],就对这一范围的数组的值作标记,看最后有多少标记,显然是会TLE的。使用线段树,其价值就在于我们不用每次都遍历[a,b]这么大范围的值,而只需要遍历线段树高度O(h)。
  2. 对于此题,线段树可以采用基本的染色原理,即对输入[a,b]在树上的区间染色,其中很重要的就是Lazy思路,虽然线段树的叶节点都是单个区间,若按最初的思路,需要对每个单个区间染色,而lazy就是直接对尽量大且是[a,b]子集的区间染色。
  3. 而当我们又要对区间染上另一种颜色时,lazy则必须改变了,我们不得不首先给区间的左右子区间染色相同颜色,与直接给它们的父区间染色是一致的,但是只有这样我们才能接下来给它的子区间染上输入的其它颜色,但是又不至于本身的颜色会消失。
  4. 离散化:若直接采用题目给出的区间1e7建立线段树,是会TLE,MLE的。而输入又说只有10000个区间输入,易知我们只关心区间的端点,而它们中间的范围是无关紧要的,所以可以采用离散化,将端点排序,使用排序后的位置作为输入区间。
此题离散化需要注意的是:前面的海报的中部本该能看到,但是离散化后的紧致性使其封闭,导致输出变小。
eg:
1
3
1 3
1 1
3 3
所以离散化时,当排序后的邻接两个实际距离相差大于1时,离散后的端点值在离散的基础上再加一。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
#define maxN 20005
#define maxS 70000

struct cmpNode
{
    int sDis;
    unsigned short segmentNo;
}sortSegment[maxN];
struct node
{
    unsigned short a,b;
    short posterNo;
}tree[maxS];
int sortSegmentNum;
int totPoster,num;
unsigned short segment[maxN];
char IsVisible[maxN];

int str2num(unsigned short sortSegmentNo)      //读优化
{
    int num = 0;
    char c;
    while((c = getchar())&&(c != ' ')&&(c != '\n')&&(c >= 0))
    {
        num *= 10;
        num += c-'0';
    }
    sortSegment[sortSegmentNo].sDis = num;
    sortSegment[sortSegmentNo].segmentNo = sortSegmentNo;
    return 0;
}

int buildTree(int nodeNo,int a,int b)
{
    int m = ((a+b)>>1);
    tree[nodeNo].a = a;
    tree[nodeNo].b = b;
    if(a < b)
    {
        buildTree(nodeNo<<1,a,m);      //左子树
        buildTree((nodeNo<<1)+1,m+1,b);     //右子树
    }
    return 0;
}

int posterInsert(int nodeNo,unsigned short s_a,unsigned short s_b,short posterNo)
{
    int m = (tree[nodeNo].a+tree[nodeNo].b)>>1;
    int left = (nodeNo<<1);
    int right = (nodeNo<<1)+1;
    if(tree[nodeNo].a == s_a&&tree[nodeNo].b == s_b)
        tree[nodeNo].posterNo = posterNo;
    else
    {
        if(tree[nodeNo].posterNo >= 0)
        {
            if(tree[nodeNo].posterNo > 0)
            {
            tree[left].posterNo = tree[nodeNo].posterNo;    //前面着色时的lazy因为要更改颜色,必须向子树染色
            tree[right].posterNo = tree[nodeNo].posterNo;
            }
            tree[nodeNo].posterNo = -1;    //表明子树是多色的
        }
        if(s_a > m)
            posterInsert(right,s_a,s_b,posterNo);   //新颜色只可能插入右子树
        else if(s_b <= m)
            posterInsert(left,s_a,s_b,posterNo);    //新颜色只可能插入左子树
        else
        {
            posterInsert(left,s_a,m,posterNo);
            posterInsert(right,m+1,s_b,posterNo);
        }
    }
    return 0;
}

int checkVisible(int nodeNo)
{
    if(tree[nodeNo].posterNo > 0)      //下面的颜色已被覆盖,则不再往子树遍历
    {
        if(!IsVisible[tree[nodeNo].posterNo])
        {
            IsVisible[tree[nodeNo].posterNo] = 1;
            num++;
        }
    }
    else if(tree[nodeNo].posterNo == -1)   //有子节点,即不能是叶节点并且没有子树被覆盖
    {
        checkVisible(nodeNo<<1);
        checkVisible((nodeNo<<1)+1);
    }
    return num;
}

int cmp(const void* a,const void* b)
{
    return (((cmpNode*)a)->sDis)-(((cmpNode*)b)->sDis);
}

int main()
{
    int c,i;
    scanf("%d",&c);
    while(c--)
    {
        num = 0;
        sortSegmentNum = 1;
        memset(tree,0,sizeof(tree));
        memset(IsVisible,0,sizeof(IsVisible));
        scanf("%d",&totPoster);
        getchar();
        for(i = 0;i < totPoster;i++)
        {
            str2num(i*2);
            str2num(i*2+1);
        }
        qsort(sortSegment,totPoster*2,sizeof(cmpNode),cmp);     //离散化
        segment[sortSegment[0].segmentNo] = sortSegmentNum;      //距离从1开始
        for(i = 1;i < totPoster*2;i++)
        {
            if(sortSegment[i].sDis-sortSegment[i-1].sDis > 0)    //排序中不等的元素则新区间值加一
            {
                sortSegmentNum++;
                if(sortSegment[i].sDis-sortSegment[i-1].sDis > 1)    //露出海报可能的中间一段
                sortSegmentNum++;
            }
            segment[sortSegment[i].segmentNo] = sortSegmentNum;
        }
        buildTree(1,1,sortSegmentNum);
        for(i = 0;i < totPoster;i++)
            posterInsert(1,segment[i*2],segment[i*2+1],i+1);
        printf("%d\n",checkVisible(1));
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值