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

题目大意:

将一些海报贴在一排的瓷砖上,这些海报的高度是一样的,且在贴的过程总保持所有海报的高度一致,海报是完整的贴在每块瓷砖上的(也就是说,如果海报覆盖了一块瓷砖,那就是完全覆盖该瓷砖),先给出张贴海报覆盖瓷砖的左边瓷砖的编号和右边瓷砖的编号(给出的顺序是按照张贴的顺序给出的)。求所有海报都张贴完之后有多少海报是完全或者部分可见的。

解题思路:

最后张贴的那张海报一定是完全可见的。本题用线段是来做,每块瓷砖对用一个线段树中的叶子节点,表示节点的结构体为:

struct CNode {               //线段树中每个结点的结构体定义
    int L, R;                //结点所表示的区间[L,R]
    bool isCover;            //结点表示的区间是否被海报完全覆盖
    CNode *pLeft, *pRight;   //左子树指针和右子树指针
};

还要注意的是:因为瓷砖的个数非常多(10,000,000),而海报的个数少(10,000),如果用线段树把瓷砖完整的区间表示出来(即:0~10,000,000),则在程序运行中会出现MLE。所以本题还要对海报的端点进行离散化,海报的个数为N,则顶点的个数最多有(2*N  10^4级别的),用线段树完全可以表示该区间(0~20,000)。

在遍历离散化后的海报区间时,必须按照从最近张贴的海报开始遍历,否则张贴的海报会影响已经张贴的海报的可见情况。

代码:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#define M 1000000
using namespace std;

struct Poster {              //海报的左边和右边
    int L, R;
};
Poster poster[10100];        //海报的数量

struct CNode {               //线段树中每个结点的结构体定义
    int L, R;                //结点所表示的区间[L,R]
    bool isCover;            //结点表示的区间是否被海报完全覆盖
    CNode *pLeft, *pRight;   //左子树指针和右子树指针
};
CNode Tree[1000000];            //Tree[i]线段树的第i个结点
int x[20200], hash[10000010];   //x[]保存所有的海报的端点值,并按从小到大的循序排序并合并重复值
                                //hash[]是海报的端点值对应的哈希值
int nNodeCount = 0;

int Mid(CNode *pNode) {
    return (pNode->R + pNode->L) / 2;
}

void BuildTree(CNode *pRoot, int l, int r) {  //建树,确定每个结点的L和R值、左右子树的指针,
                                              //初始化isCover为false:结点表示的
                                              //区间没有被海报完全覆盖,
    pRoot->L = l; pRoot->R = r;
    pRoot->isCover = false;
    if(l == r) return ;

    nNodeCount ++;
    pRoot->pLeft = Tree + nNodeCount;
    nNodeCount ++;
    pRoot->pRight = Tree + nNodeCount;
    BuildTree(pRoot->pLeft, l, Mid(pRoot));
    BuildTree(pRoot->pRight, Mid(pRoot)+1, r);
}

bool Query(CNode *pRoot, int l, int r) {  //每贴一张海报,检查海报的[l,r]是否已经被其他海报完全覆盖
    if(pRoot->isCover) return false;      //如果pRoot结点表示的区间(肯定覆盖了[l,r]区间)被完全覆盖,
                                          //直接返回false
    if(pRoot->L == l && pRoot->R == r) {  //能够走到这段代码,说明[l,r]没有被完全覆盖
                                          //如果海报的[l,r]和结点pRoot表示的区间想吻合,更新该结点
                                          //的isCover的值。表示该顶点表示的区间被该海报完全覆盖
        pRoot->isCover = true;
        return true;
    }
    
    //[l,r]真包含于[pRoot->L, pRoot->R]
    bool Result;
    if(r <= Mid(pRoot)) {                       //线段树常用语法,如果(l,r)在pRoot的左子树,则进入左子树
        Result = Query(pRoot->pLeft, l, r);
    }
    else if(l > Mid(pRoot)) {                   //进入右子树
        Result = Query(pRoot->pRight, l, r);
    }
    else {                                      //部分进入左子树,部分进入右子树
        bool b1 = Query(pRoot->pLeft, l, Mid(pRoot));
        bool b2 = Query(pRoot->pRight, Mid(pRoot)+1, r);
        Result = b1 || b2;
        //Result = Query(pRoot->pLeft, l, Mid(pRoot)) || Query(pRoot->pRight, Mid(pRoot)+1, r);
        //注释的部分是错误的,如果第一个Query()为true,后面的Query()函数就不会执行
    }
    if(pRoot->pLeft->isCover && pRoot->pRight->isCover)
        pRoot->isCover = true;
    return Result;
}

int main() {
    int t, nCount;      //nCount=所有海报端点值(l和r)的个数
    scanf("%d", &t);
    while(t--) {
        int n;
        scanf("%d", &n);
        nCount = 0;
        for(int i=0; i<n; i++) {
            scanf("%d%d", &poster[i].L, &poster[i].R);
            x[nCount++] = poster[i].L;
            x[nCount++] = poster[i].R;
        }
        sort(x, x+nCount);
        nCount = unique(x, x+nCount) - x; //合并x[]中重复的元素
        int nIntervalNo = 0;
        for(int i=0; i<nCount; i++) {     //将每一个x[i]离散化,因为x[i]的值很大(10^7),如果用x[i]的值表示的
                                          //区间建树BuildTree(Tree, 0, max(x[i])),则程序在运行过程中会MLE
            hash[x[i]] = nIntervalNo;
            if(i < nCount - 1) {
                if(x[i+1] == x[i]+1)
                    nIntervalNo ++;
                else nIntervalNo += 2;  //相邻x[i]和x[i+1]的值相差超过1,则忽略两个数过大的差值,将其差值
                                        //认为是2,形象一点:如果几张海报都覆盖了连续的几块瓷砖,则任务他们
                                        //值覆盖了两个瓷砖,即把该连续的几块瓷砖认为两块瓷砖。
            }
        }
        nNodeCount = 0;
        BuildTree(Tree, 0, nIntervalNo);    //建树
        int ans = 0;
        for(int i=n-1; i>=0; i--) {         //从最近张贴的海报遍历所有的海报。从n-1~0遍历海报可以确保对于已经
                                            //遍历过的海报不会受到还没有遍历过的海报的影响
            if(Query(Tree, hash[poster[i].L], hash[poster[i].R]))
                ans ++;
        }
        printf("%d\n", ans);
    }
    return 0;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值