题目
思路
看上去像一个 三维偏序 问题,即
x
,
y
,
t
i
m
e
x,y,time
x,y,time 三个维度。请忘掉
k
-
d
k\text-d
k-d 树,因为 我真的不会。
想办法用扫描线干掉一维。对 t i m e time time 扫描线,恕我直言,完全是脑子瓦特了。因为 t i m e time time 是一个简单的维度。与之相对的, x x x 与 y y y 都是区间操作,更复杂。
不妨对 x x x 扫描线。那么 y y y 需要一个数据结构解决一维区间问题。优先选择线段树。那么线段树的节点就需要维护一些与 t i m e time time 有关的值,来支持下面两种操作:
- 加入一个矩形,并查看它是否能被看到。
- 删除一个矩形,并检查这是否让一些矩形露了出来。
先解决第一个操作。一个矩形什么情况下会被看见呢?其 t i m e time time 超过 区间内能看到的矩形的 min t i m e \min time mintime 即可。这东西感觉很好维护,是吧?——它怎么支持删除操作呢?
不妨请出我们的老朋友,标记永久化。所有标记都是 “有一个矩形完全覆盖了这个区间”,维护的 m n mn mn 是 子树内的 min t i m e \min time mintime,查询时是自顶向下,顺便把一条链上的信息叠加起来就行。标记可以用 s e t \rm set set 存储。更新都用最大值,因为 m n mn mn 是子树内 可看到的颜色 的最小值。
现在考虑解决第二个问题。其实一个节点只在乎子节点和父节点的信息。在删除一个节点的时候,其父节点会即刻更新,不用担心,问题只在于子节点。打一个懒标记就可以了,表示 “把子树内的所有点,按照根节点到当前点的 l a z y = max t i m e lazy=\max time lazy=maxtime 来更新一次” 即可。这个标记可以合并,也可以下传,完全没问题。
复杂度呢?加入一个矩形,相当于线段树套平衡树,所以复杂度是
O
(
n
log
2
n
)
\mathcal O(n\log^2 n)
O(nlog2n) 的。删除一个矩形,显然不能访问所有标记。只能再开一个
s
e
t
\rm set
set 存储 对应到这个节点的没露出的矩形,那么每个标记会用
O
(
log
n
)
\mathcal O(\log n)
O(logn) 的时间删掉,一共
O
(
n
log
n
)
\mathcal O(n\log n)
O(nlogn) 个标记,总复杂度
O
(
n
log
2
n
)
\mathcal O(n\log^2 n)
O(nlog2n)
由于只需要取最大值,用堆来实现是一样的。
代码
竟然是……离散化!打错了!这种问题一定要把 y y y 和 y − 1 y-1 y−1 都丢进去!
否则,一个覆盖了 y 1 y_1 y1,另一个覆盖了 y 2 y_2 y2,只要二者中间没有别的 y y y,就会被判定为 “相邻” 了!要加入 y 2 − 1 y_2-1 y2−1 来避免。
另:我的写法中 p u s h D o w n \rm pushDown pushDown 是要管当前节点的,所以叶子节点也必须清空标记(不能直接 r e t u r n \rm return return 走人)。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int infty = (1<<30)-1;
struct BestHeap{
priority_queue< int > pq, bad;
void push(int x){ pq.push(x); }
void erase(int x){ bad.push(x); }
void pop(){ pq.pop(); }
void clear(){
while(!pq.empty()) pq.pop();
while(!bad.empty()) bad.pop();
}
bool empty(){
while(!pq.empty() && !bad.empty())
if(pq.top() == bad.top())
pq.pop(), bad.pop();
else if(bad.top() > pq.top())
bad.pop(); // useless
else break; // no more
return pq.empty();
}
int top(){
if(!empty()) return pq.top();
return -infty; // minimum
}
};
const int MaxN = 400005;
int n, m; bool good[MaxN];
namespace SgTree{
const int MaxM = MaxN<<2;
BestHeap all[MaxM], s[MaxM];
int lazy[MaxM], mn[MaxM];
void pushUp(int o,int l,int r){
if(l != r) // not a leaf
mn[o] = min(mn[o<<1],mn[o<<1|1]);
else mn[o] = 0; // background
mn[o] = max(mn[o],all[o].top());
}
void pushDown(int o,int l,int r){
if(lazy[o] > n) return ;
int t = max(lazy[o],mn[o]);
for(; s[o].top()>=t; s[o].pop())
good[s[o].top()] = true;
if(l != r){
t = max(lazy[o],all[o].top());
lazy[o<<1] = min(lazy[o<<1],t);
lazy[o<<1|1] = min(lazy[o<<1|1],t);
}
lazy[o] = infty; // ineffective
}
void add(int ql,int qr,int qv,int pre=0,int o=1,int l=1,int r=m){
pushDown(o,l,r); // important
if(ql <= l && r <= qr){
if(max(pre,mn[o]) <= qv)
good[qv] = true;
else s[o].push(qv);
all[o].push(qv);
return pushUp(o,l,r); // update info
}
int mid = (l+r)>>1; pre = max(pre,all[o].top());
if(ql <= mid) add(ql,qr,qv,pre,o<<1,l,mid);
if(mid < qr) add(ql,qr,qv,pre,o<<1|1,mid+1,r);
return pushUp(o,l,r); // update info
}
void del(int ql,int qr,int qv,int pre=0,int o=1,int l=1,int r=m){
pushDown(o,l,r); // important
if(ql <= l && r <= qr){
s[o].erase(qv), all[o].erase(qv);
lazy[o] = pre; return pushUp(o,l,r);
}
int mid = (l+r)>>1, t = max(pre,all[o].top());
if(ql <= mid) del(ql,qr,qv,t,o<<1,l,mid);
if(mid < qr) del(ql,qr,qv,t,o<<1|1,mid+1,r);
pushUp(o,l,r); t = max(pre,mn[o]);
for(; s[o].top()>=t; s[o].pop())
good[s[o].top()] = true;
}
void clear(int o=1,int l=1,int r=m){
lazy[o] = infty, mn[o] = 0;
all[o].clear(), s[o].clear();
if(l == r) return ;
clear(o<<1,l,(l+r)>>1);
clear(o<<1|1,(l+r)/2+1,r);
}
}
struct Command{
int opt, ly, ry, id, x;
bool operator < (const Command &t) const {
if(x != t.x) return x < t.x;
if(opt != t.opt)
return opt > t.opt;
return opt*id > opt*t.id;
}
};
Command q[MaxN];
int item[MaxN];
int LiSan(int* l,int* r){
memcpy(item,l,(r-l)<<2);
sort(item,item+(r-l));
int *q = unique(item,item+(r-l));
for(int* i=l; i!=r; ++i)
*i = lower_bound(item,q,*i)-item+1;
return q-item; // cnt of distinct
}
int tmpy[MaxN];
int main(){
n = readint();
for(int i=0; i<n; ++i){
q[i<<1].x = readint();
tmpy[i<<1] = readint();
q[i<<1|1].x = readint();
tmpy[i<<1|1] = readint()-1;
tmpy[(n<<1)+i] = tmpy[i<<1|1]+1;
}
m = LiSan(tmpy,tmpy+n*3);
for(int i=0; i<n; ++i){
q[i<<1].opt = 1, q[i<<1|1].opt = -1;
q[i<<1].ly = q[i<<1|1].ly = tmpy[i<<1];
q[i<<1].ry = q[i<<1|1].ry = tmpy[i<<1|1];
q[i<<1].id = q[i<<1|1].id = i+1;
}
sort(q,q+(n<<1)); // sort them
SgTree::clear();
for(int i=0; i<(n<<1); ++i){
if(q[i].opt == 1)
SgTree::add(q[i].ly,q[i].ry,q[i].id);
else SgTree::del(q[i].ly,q[i].ry,q[i].id);
}
int ans = 1; // background
for(int i=1; i<=n; ++i)
ans += good[i];
printf("%d\n",ans);
return 0;
}