|
|
|
|
## [$P3740$ $[HAOI2014]$ 贴海报](https://www.luogu.com.cn/problem/P3740)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
|
|
|
|
|
$Bytetown$城市要进行市长竞选,所有的选民可以畅所欲言地对竞选市长的候选人发表言论。为了统一管理,城市委员会为选民准备了一个张贴海报的$electoral$墙。
|
|
|
|
|
|
|
|
|
|
张贴规则如下:
|
|
|
|
|
|
|
|
|
|
1. $electoral$墙是一个长度为$N$个单位的长方形,每个单位记为一个格子;
|
|
|
|
|
|
|
|
|
|
2. 所有张贴的海报的高度必须与$electoral$墙的高度一致的;
|
|
|
|
|
|
|
|
|
|
3. 每张海报以“$A$ $B$”表示,即从第$A$个格子到第$B$个格子张贴海报;
|
|
|
|
|
|
|
|
|
|
4. 后贴的海报可以覆盖前面已贴的海报或部分海报。
|
|
|
|
|
|
|
|
|
|
现在请你判断,张贴完所有海报后,在$electoral$墙上还可以看见多少张海报。
|
|
|
|
|
|
|
|
|
|
#### 输入格式
|
|
|
|
|
|
|
|
|
|
第一行: $N$ $M$ 分别表示$electoral$墙的长度和海报个数
|
|
|
|
|
|
|
|
|
|
接下来$M$行: $A_i B_i$ 表示每张海报张贴的位置
|
|
|
|
|
|
|
|
|
|
#### 输出格式
|
|
|
|
|
|
|
|
|
|
输出贴完所有海报后,在$electoral$墙上还可以看见的海报数。
|
|
|
|
|
|
|
|
|
|
#### 样例输入 #1
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
100 5
|
|
|
|
|
1 4
|
|
|
|
|
2 6
|
|
|
|
|
8 10
|
|
|
|
|
3 4
|
|
|
|
|
7 10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 样例输出 #1
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
4
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### 提示
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
【约束条件】
|
|
|
|
|
|
|
|
|
|
$0<= N <= 10000000, 1<=M<=1000 , 1<= A_i <= B_i <=10000000$
|
|
|
|
|
|
|
|
|
|
所有的数据都是整数。数据之间有一个空格
|
|
|
|
|
|
|
|
|
|
### 二、解题思路
|
|
|
|
|
这是一道 **区间染色** 类的题目。
|
|
|
|
|
|
|
|
|
|
**可以用线段树来维护一个$bool$标记,来记录该点所辖区间是否完整被覆盖。**
|
|
|
|
|
|
|
|
|
|
我们可以 **从右向左遍历** 全部的海报所辖区间(因为贴海报是后面的覆盖前面的),每次将这个区间放入线段树,即将线段树中的$[l,r]$区间内所有为$false$的地方改为$true$,但是如果该区间内的点已经全部被标记过了,那么说明这张海报不会露出来。
|
|
|
|
|
|
|
|
|
|
最后统计所有露出来的海报即为最终答案。
|
|
|
|
|
|
|
|
|
|
而由于区间范围比较大$<=1e7$,要 **离散化** 区间。
|
|
|
|
|
|
|
|
|
|
需要注意的是,由于是 **区间覆盖** 问题,所以普通离散化会出问题,比如:
|
|
|
|
|
$[1,6],[1,3],[5,6]$离散化后会变为$[1,4],[1,2],[3,4]$,这样一来,原来第一个区间就会被完全覆盖,其实人家的$[4]$是可以被看到的,没有海报可以盖住$4$,你离散化后就不对了,**离散化失败**。
|
|
|
|
|
|
|
|
|
|
这是因为,离散化操作让不相邻的点变得相邻了,这在普通问题中没有什么影响,但在 **区间覆盖** 问题上就变得很关键了。
|
|
|
|
|
|
|
|
|
|
所以我们要在离散化数组中, **插入$r[i]+1$,防止后续点不相邻的点在离散化后和它相邻** 。
|
|
|
|
|
|
|
|
|
|
### $Code$
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 1e3 + 10;
|
|
|
|
|
int n, m;
|
|
|
|
|
int x[N], y[N];
|
|
|
|
|
bool flag; // 当前枚举到的海报是不是能被看到
|
|
|
|
|
|
|
|
|
|
// 推荐版本:静态离散化+区间覆盖
|
|
|
|
|
#define ls (u << 1)
|
|
|
|
|
#define rs (u << 1 | 1)
|
|
|
|
|
#define mid ((l + r) >> 1)
|
|
|
|
|
struct Node {
|
|
|
|
|
int l, r;
|
|
|
|
|
bool color; // 是不是所辖区间被完整覆盖
|
|
|
|
|
} tr[N << 2];
|
|
|
|
|
|
|
|
|
|
void pushup(int u) {
|
|
|
|
|
tr[u].color = tr[ls].color && tr[rs].color; // 只有左儿子被完整覆盖,并且,右儿子被完整覆盖,自己才是被完整覆盖
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void build(int u, int l, int r) {
|
|
|
|
|
tr[u].l = l, tr[u].r = r;
|
|
|
|
|
if (l == r) return;
|
|
|
|
|
build(ls, l, mid);
|
|
|
|
|
build(rs, mid + 1, r);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void modify(int u, int L, int R) {
|
|
|
|
|
int l = tr[u].l, r = tr[u].r;
|
|
|
|
|
if (l > R || r < L) return;
|
|
|
|
|
if (tr[u].color) return; // 如果本区间原来就已经被覆盖过了,那么返回,也就不会在这段区间使得flag=true,也就是这个区域是看不到的
|
|
|
|
|
if (l >= L && r <= R) { // 如果以前没有被覆盖过,并且,本次本区域被完整覆盖,也就是第一次覆盖
|
|
|
|
|
flag = true; // 当前枚举到的海报可以被看到
|
|
|
|
|
tr[u].color = true; // 记录本区域被完整覆盖
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
modify(ls, L, R), modify(rs, L, R);
|
|
|
|
|
pushup(u);
|
|
|
|
|
}
|
|
|
|
|
int b[N << 1], bl; // 离散化数组
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
#ifndef ONLINE_JUDGE
|
|
|
|
|
freopen("P3740.in", "r", stdin);
|
|
|
|
|
#endif
|
|
|
|
|
// 加快读入
|
|
|
|
|
ios::sync_with_stdio(false), cin.tie(0);
|
|
|
|
|
cin >> n >> m;
|
|
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
|
|
cin >> x[i] >> y[i];
|
|
|
|
|
b[++bl] = x[i];
|
|
|
|
|
b[++bl] = y[i];
|
|
|
|
|
b[++bl] = y[i] + 1; // 区间覆盖问题的解决之道
|
|
|
|
|
}
|
|
|
|
|
// 排序+去重 (要注意b数组的范围,bl代表了它的上界!)
|
|
|
|
|
sort(b + 1, b + 1 + bl);
|
|
|
|
|
int tot = unique(b + 1, b + 1 + bl) - b - 1;
|
|
|
|
|
|
|
|
|
|
// 构建线段树
|
|
|
|
|
build(1, 1, tot);
|
|
|
|
|
|
|
|
|
|
int res = 0;
|
|
|
|
|
for (int i = m; i; i--) { // 倒序
|
|
|
|
|
flag = false; // 当前枚举到的海报是不是能被看到,默认是不能被看到
|
|
|
|
|
int L = lower_bound(b + 1, b + 1 + tot, x[i]) - b;
|
|
|
|
|
int R = lower_bound(b + 1, b + 1 + tot, y[i]) - b;
|
|
|
|
|
modify(1, L, R); // 覆盖掉【L,R】
|
|
|
|
|
if (flag) res++; // 如果当前枚举到的海报可以被看到,那么多了一张
|
|
|
|
|
}
|
|
|
|
|
// 输出答案
|
|
|
|
|
printf("%d\n", res);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 三、珂朵莉树解法
|
|
|
|
|
不懂珂朵莉树的同学可以看看$oi$ $wiki$的[介绍](https://oi-wiki.org/ds/odt/)
|
|
|
|
|
|
|
|
|
|
简单来说珂朵莉树的原理就是维护拥有相同元素的区间
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
struct Node {
|
|
|
|
|
int l, r;
|
|
|
|
|
mutable int v;
|
|
|
|
|
const bool operator<(const Node &b) const{
|
|
|
|
|
return l < b.l;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
具体到本题就是将一整张海报的区间当作一个节点,每个不同的海报的区间染上不同的颜色,加入新的海报就相当于珂朵莉树的 $assign$ 操作
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
最后暴力的扫一遍$[1, 0]$ (整个墙)这个区间 记录访问到的颜色的种类(初始时整个墙围一整个区间 颜色$v$为$0$)
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
const int N = 1010;
|
|
|
|
|
|
|
|
|
|
// 用于记录颜色的集合
|
|
|
|
|
set<int> colors;
|
|
|
|
|
|
|
|
|
|
// 柯朵莉树模板
|
|
|
|
|
struct Node {
|
|
|
|
|
int l, r;
|
|
|
|
|
mutable int v;
|
|
|
|
|
bool operator<(const Node &b) const {
|
|
|
|
|
return l < b.l;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
set<Node> s;
|
|
|
|
|
|
|
|
|
|
// 分裂:[l,x-1],[x,r]
|
|
|
|
|
set<Node>::iterator split(int x) {
|
|
|
|
|
auto it = s.lower_bound({x});
|
|
|
|
|
if (it != s.end() && it->l == x) return it; // 如果x就是一个区间的开头,就不用切割,直接返回这个区间
|
|
|
|
|
|
|
|
|
|
it--;
|
|
|
|
|
if (it->r < x) return s.end(); // x太大,超过了最后一个区间的右端点,直接返回s.end()
|
|
|
|
|
|
|
|
|
|
int l = it->l, r = it->r, v = it->v;
|
|
|
|
|
s.erase(it); // 删掉旧区间
|
|
|
|
|
s.insert({l, x - 1, v}); // 拆分成两个区间
|
|
|
|
|
return s.insert({x, r, v}).first; // 返回新插入区间位置的迭代器
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add(int l, int r, int v) {
|
|
|
|
|
auto R = split(r + 1), L = split(l);
|
|
|
|
|
for (auto it = L; it != R; it++) it->v += v;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assign(int l, int r, int v) {
|
|
|
|
|
auto R = split(r + 1), L = split(l);
|
|
|
|
|
s.erase(L, R);
|
|
|
|
|
s.insert({l, r, v});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 计算有多少种可见颜色
|
|
|
|
|
int countColor(int l, int r) {
|
|
|
|
|
auto R = split(r + 1), L = split(l);
|
|
|
|
|
for (auto it = L; it != R; it++)
|
|
|
|
|
if (it->v) colors.insert(it->v);
|
|
|
|
|
return colors.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main() {
|
|
|
|
|
// 加快读入
|
|
|
|
|
ios::sync_with_stdio(false), cin.tie(0);
|
|
|
|
|
|
|
|
|
|
int n, m;
|
|
|
|
|
cin >> n >> m;
|
|
|
|
|
|
|
|
|
|
// 初始时整个区间颜色id为0
|
|
|
|
|
s.insert({1, n, 0});
|
|
|
|
|
|
|
|
|
|
// m个区间统一赋值为i
|
|
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
|
|
int l, r;
|
|
|
|
|
cin >> l >> r;
|
|
|
|
|
assign(l, r, i); // 给海报区间染色 每个区间颜色不同
|
|
|
|
|
}
|
|
|
|
|
// 输出有几种可见的颜色
|
|
|
|
|
cout << countColor(1, n) << endl;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|