[CF983D]Arkady and Rectangles

这种 sb 题目描述完全就是拿来坑人的好吧......

壹、题目描述

传送门 to CF

贰、题解

我们先有一个树套树的做法,很暴力,但是复杂度是 \(\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] 要在 mxcmin(minn[ls], minn[rs]) 中取最大值而非最小值?这是为了解决两种情况:

  1. 覆盖这个区间的颜色太大了,直接把两个子区间的颜色盖掉,大盖小;
  2. 覆盖两个子区间的颜色太大了,拼起来把这个区间颜色盖掉,小盖大;

对于每扫描完一个 \(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\)

某些关于矩阵的操作,我们除了直接使用二维数据结构维护以外,可以考虑使用扫描线等思想处理掉一维,然后用数据结构维护另外一维。

posted @ 2021-02-24 22:16  Arextre  阅读(98)  评论(0编辑  收藏  举报