一、例题(Mayor’s posters)
原题链接:https://vjudge.net/contest/479523#problem/G
1、题意
有一堵墙,往上面贴海报,每张海报有各自的范围,可以相互覆盖,求最后还能看见多少张海报?
2、输入格式
第一行一个整数n表示数据组数
每组数据第一行一个整数m表示海报数量
接下来m行每行两个整数l,r表示海报覆盖的范围
3、输出格式
一个整数表示最后还能看见的海报数量
4、样例
sample input1
1
5
1 4
2 6
8 10
3 4
7 10
sample output1
4
例题一题解
1、分析
(1)离散化
本题墙的长度数据过大,且题目答案与区间长度无关,只与数量有关,可以使用离散化将其区间缩小
离散化适用于,在整数集合z中涉及其中m个有限数值,且题目答案与所数值的绝对大小无关,只和他们的相对大小有关(或只和他们的相对顺序有关)
此时我们可以将z中的m个整数与1-m建立映射关系(一一对应)
此举可以大大降低时间复杂度和空间复杂度
c++具有stl函数unique来排除有序数组中的重复数字,该函数返回数组去重后,不重复部分最后一个数字的地址
(2)染色
对于本题,我们定义数字0-m代表m种颜色,保存在懒标记中,当某个节点存在懒标记,则表示当前情况该节点的所有子节点都被第x种颜色覆盖,即第x种颜色在该区间最上方
每次对懒标记进行修改或者对线段树进行查询时都要确认某点是否具有懒标记,具有懒标记则需要执行懒标记下传操作
(3)本题细节
本题中,直接进行离散化会导致情况遗漏
如上图,我们先将所有数字存入num数组中排序并使用unique排除重复数字,得到最后一个数字下标为3
我们可以发现,原本后两张海报中间应当有一个空缺位,正确答案为3张海报,直接离散后空缺位消失,答案变为错误的2
因此我们需要在数值4和6之间插入一个数值4+1,也就是循环一次有效部分,每次找到相邻两数之差( n u m [ i ] − n u m [ i − 1 ] num[i]-num[i-1] num[i]−num[i−1])大于1时,在下标 ++len处添加一个值 n u m [ i − 1 ] + 1 num[i - 1] + 1 num[i−1]+1
最后再 ++len处插入一个0,便于将num的下标0占位,对0-len的num数组范围进行排序,最后得到的num数组的1 - len就是最终的离散化数组
2、代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 2e5 + 5;
int lx[maxn * 10], rx[maxn * 10], num[maxn * 10];
int vis[maxn * 10];
struct tt
{
int l, r, la;
} tree[maxn * 10];
void lazy(int k)//下传懒标记
{
tree[k << 1].la = tree[k << 1 | 1].la = tree[k].la;
tree[k].la = -1;
}
void build(int p, int l, int r)//建树
{
tree[p].l = l;
tree[p].r = r;
tree[p].la = -1;
if (l == r)
return;
int mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
}
void change(int k, int l, int r, int c) // c为地毯颜色
{
if (tree[k].l == l && tree[k].r == r)
{
tree[k].la = c;
return;
}
if (tree[k].la != -1)
{
lazy(k);
}
int mid = tree[k].l + tree[k].r >> 1;
if (mid >= r)
{
change(k << 1, l, r, c);
}
else if (mid < l)
{
change(k << 1 | 1, l, r, c);
}
else
{
change(k << 1, l, mid, c);
change(k << 1 | 1, mid + 1, r, c);
}
}
int ans=0;
void query(int l, int r, int k)
{
if (tree[k].la != -1)
{
if (!vis[tree[k].la])//标记颜色(海报)可见
{
ans++;
vis[tree[k].la] = 1;
}
return;
}
if (l == r)
return;
int mid = tree[k].l + tree[k].r >> 1;
query(l, mid, k << 1);
query(mid + 1, r, k << 1 | 1);
}
int main()
{
int n;
cin >> n;
while (n--)
{
int m, tot = 0, temp1=0; // tot左右区间所有的数字总数,temp1 tot去重之后的数字总数
cin >> m;
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
for (int i = 0; i < m; ++i)
{
cin >> lx[i] >> rx[i];
num[tot++] = lx[i];
num[tot++] = rx[i]; //存储所有左右区间的数字
}
sort(num, num + tot);
temp1 = unique(num , num + tot) - num;//unique将num中重复数字移到数组末尾并返回有效部分地址
int temp2 = temp1;
for (int i = 1; i < temp2; ++i)//防止遗漏情况,两数之差大于1时中间插一个数
{
if (num[i] - num[i - 1] > 1)
{
num[temp1++] = num[i - 1] + 1;
}
}
num[temp1++] = 0;//末尾插入一个0,便于sort时将0占位
sort(num, num + temp1);
build(1, 1, temp1);//建树
int l, r, c;
for (int i = 0; i < m; ++i)//上色(贴海报)
{
l = lower_bound(num, num + temp1, lx[i]) - num;
r = lower_bound(num, num + temp1, rx[i]) - num;
c = i;
change(1, l, r, c);
}
ans = 0;
query(1, temp1, 1);
cout << ans << endl;
}
}