[CF983D]Arkady and Rectangles
这种 sb 题目描述完全就是拿来坑人的好吧......
壹、题目描述
贰、题解
我们先有一个树套树的做法,很暴力,但是复杂度是 \(\mathcal O(n\log^2 n)\),空间不知道是否过得去,这里不多说 我也打不来 。
我们考虑用扫描线扫过 \(x\) 这一维,对于 \(y\) 使用线段树维护。
线段树的每个点维护三个东西:
- \(\rm maxx\),表示这个区间最大的并且没有被算入答案的颜色标号;
- \(\rm minn\),表示这个区间最小的并且可视的颜色;
- \(S\),一个 \(set\) 集合,容纳的是完全覆盖了这个区间颜色的编号;
不难发现,当 \(\rm maxx<minn\) 的时候,这个区间的 \(\rm maxx\) 是不可视的,我们就把这个 \(\rm maxx\) 赋值为 \(0\).
其他情况,我们可以按照以下代码进行更新:
void upload(const int i, const int l, const int r){
int mxc=*s[i].rbegin();
if(l==r) maxx[i]=minn[i]=mxc;
else{
maxx[i]=max(mxc, max(maxx[ls], maxx[rs]));
minn[i]=max(mxc, min(minn[ls], minn[rs]));
}
if(vis[maxx[i]] || maxx[i]<minn[i])
maxx[i]=0;
}
至于 minn[i]=max(mxc, min(minn[ls], minn[rs]))
,为什么对于这个 minn[i]
要在 mxc
与 min(minn[ls], minn[rs])
中取最大值而非最小值?这是为了解决两种情况:
- 覆盖这个区间的颜色太大了,直接把两个子区间的颜色盖掉,大盖小;
- 覆盖两个子区间的颜色太大了,拼起来把这个区间颜色盖掉,小盖大;
对于每扫描完一个 \(x\),我们可以直接判断线段是的根节点的 \(\tt maxx[]\) 是否为 \(0\),如果非 \(0\),则说明这个颜色可视,那么我们将这个颜色打上 \(\tt vis\) 标记之后,要将这个颜色所涉及的区间重新进行一次上传 \(\tt (upload)\) 操作,但是注意,我们不能直接将其删掉,因为它可能还挡住了其他的颜色 (占着茅坑不拉屎) 。
所以这里重新定义了一个 \(\tt update()\) 函数,如下:
void update(const int L, const int R, const int i, const int l, const int r){
if(L<=l && r<=R) return upload(_this);
if(L<=mid) update(L, R, _lq);
if(mid<R) update(L, R, _rq);
upload(_this);
}
其他的都是基操了。
叁、参考代码
#include<cstdio>
#include<algorithm>
#include<set>
using namespace std;
namespace Elaina{
#define rep(i, l, r) for(int i=l, i##_end_=r; i<=i##_end_; ++i)
#define fep(i, l, r) for(int i=l, i##_end_=r; i>=i##_end_; --i)
#define fi first
#define se second
#define Endl putchar('\n')
#define writc(x, c) fwrit(x), putchar(c)
typedef long long ll;
typedef pair<int, int> pii;
typedef unsigned long long ull;
typedef unsigned int uint;
template<class T>inline T Max(const T x, const T y){return x<y? y: x;}
template<class T>inline T Min(const T x, const T y){return x<y? x: y;}
template<class T>inline T fab(const T x){return x<0? -x: x;}
template<class T>inline void getMax(T& x, const T y){x=Max(x, y);}
template<class T>inline void getMin(T& x, const T y){x=Min(x, y);}
template<class T>T gcd(const T x, const T y){return y? gcd(y, x%y): x;}
template<class T>inline T readin(T x){
x=0; int f=0; char c;
while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
return f? -x: x;
}
template<class T>void fwrit(const T x){
if(x<0) return putchar('-'), fwrit(-x);
if(x>9) fwrit(x/10); putchar(x%10^48);
}
}
using namespace Elaina;
const int maxn=1e5;
int yl[maxn+5], yr[maxn+5];
struct node{
int x, l, r, id, opt;
node(){}
node(const int X, const int L, const int R, const int I, const int O):
x(X), l(L), r(R), id(I), opt(O){}
inline int operator <(const node rhs) const{
return x<rhs.x;
}
}q[maxn*2+5];
int n, cnt;
int y[maxn*6+5], cnty;
// whether this node has been count up into the answer
int vis[maxn+5];
namespace segment_tree{
// record the color that cover this interval completely
set<int>s[maxn*24+5];
// the maximum visible color which hasn't been counted up into the answer
int maxx[maxn*24+5];
// the minimum color which is visible in this interval, no limited condition
// so that if a color is larger than it, we say this color is visible in this interval
int minn[maxn*24+5];
// insert a color
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
#define _lq ls, l, mid
#define _rq rs, mid+1, r
#define _this i, l, r
void build(const int i, const int l, const int r){
s[i].insert(0); minn[i]=maxx[i]=0;
if(l==r) return;
build(_lq), build(_rq);
}
void upload(const int i, const int l, const int r){
int mxc=*s[i].rbegin();
if(l==r) maxx[i]=minn[i]=mxc;
else{
maxx[i]=max(mxc, max(maxx[ls], maxx[rs]));
minn[i]=max(mxc, min(minn[ls], minn[rs]));
}
if(vis[maxx[i]] || maxx[i]<minn[i])
maxx[i]=0;
}
void modify(const int c, const int L, const int R, const int type, const int i, const int l, const int r){
if(L<=l && r<=R){
if(type) s[i].insert(c);
else s[i].erase(c);
return upload(_this);
}
if(L<=mid) modify(c, L, R, type, _lq);
if(mid<R) modify(c, L, R, type, _rq);
upload(_this);
}
void update(const int L, const int R, const int i, const int l, const int r){
if(L<=l && r<=R) return upload(_this);
if(L<=mid) update(L, R, _lq);
if(mid<R) update(L, R, _rq);
upload(_this);
}
#undef ls
#undef rs
#undef mid
#undef _lq
#undef _rq
}
inline void input(){
n=readin(1);
int x1, x2;
for(int i=1; i<=n; ++i){
x1=readin(1), yl[i]=readin(1);
x2=readin(1)-1, yr[i]=readin(1)-1;
q[++cnt]=node(x1, yl[i], yr[i], i, 1);
q[++cnt]=node(x2+1, yl[i], yr[i], i, 0);
y[++cnty]=yl[i]-1, y[++cnty]=yl[i], y[++cnty]=yl[i]+1;
y[++cnty]=yr[i]-1, y[++cnty]=yr[i], y[++cnty]=yr[i]+1;
}
}
inline void hashy(){
sort(y+1, y+cnty+1);
cnty=unique(y+1, y+cnty+1)-y-1;
for(int i=1; i<=cnt; ++i){
q[i].l=lower_bound(y+1, y+cnty+1, q[i].l)-y;
q[i].r=lower_bound(y+1, y+cnty+1, q[i].r)-y;
}
for(int i=1; i<=n; ++i){
yl[i]=lower_bound(y+1, y+cnty+1, yl[i])-y;
yr[i]=lower_bound(y+1, y+cnty+1, yr[i])-y;
}
}
inline void scan_line(){
sort(q+1, q+cnt+1);
segment_tree::build(1, 1, cnty);
int ans=0;
for(int i=1; i<=cnt; ++i){
int j=i;
while(q[j].x==q[i].x){
segment_tree::modify(q[j].id, q[j].l, q[j].r, q[j].opt, 1, 1, cnty);
++j;
}
i=j-1;
while(segment_tree::maxx[1]){
int c=segment_tree::maxx[1];
++ans, vis[c]=1;
segment_tree::update(yl[c], yr[c], 1, 1, cnty);
}
}
printf("%d\n", ans+1);
}
signed main(){
input();
hashy();
scan_line();
return 0;
}
注意,扫描线每次要将相同的 \(x\) 处理完之后才跳出。
同时,输入的是点编号而非格子编号,所以 \(x_2,y_2\) 需要进行一些改动。
还有,由于我们的撤销 \(\tt (opt=0)\) 的操作是在访问完矩形之后的,所以撤销操作应该是在 \(x_2+1\) 处执行而非 \(x_2\) 处执行。
肆、用到の小 \(\tt trick\)
某些关于矩阵的操作,我们除了直接使用二维数据结构维护以外,可以考虑使用扫描线等思想处理掉一维,然后用数据结构维护另外一维。