题目大意:
将一些海报贴在一排的瓷砖上,这些海报的高度是一样的,且在贴的过程总保持所有海报的高度一致,海报是完整的贴在每块瓷砖上的(也就是说,如果海报覆盖了一块瓷砖,那就是完全覆盖该瓷砖),先给出张贴海报覆盖瓷砖的左边瓷砖的编号和右边瓷砖的编号(给出的顺序是按照张贴的顺序给出的)。求所有海报都张贴完之后有多少海报是完全或者部分可见的。
解题思路:
最后张贴的那张海报一定是完全可见的。本题用线段是来做,每块瓷砖对用一个线段树中的叶子节点,表示节点的结构体为:
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;
}