[JOISC 2014 Day3]稻草人

题目

传送门 to LOJ

题目概要
n n n 个点 ( x i , y i ) (x_i,y_i) (xi,yi) ,选择其中两个点,作为一个矩形的左下角、右上角,求有多少个这样的矩形满足:内部不包含其他点。

数据范围与约定
n ≤ 2 × 1 0 5 , x i ≠ x j , y i ≠ y j n\le2\times 10^5,x_i\ne x_j,y_i\ne y_j n2×105,xi=xj,yi=yj

思路

刚拿到这道题,确实感觉没有什么好做法,不像其他题解里说的那样“显然”。

简化版

对于一个给定的 p ∈ [ 1 , n ] p\in[1,n] p[1,n] ,求以 ( x p , y p ) (x_p,y_p) (xp,yp) 为左下角的矩形个数。

等价转换一下题意:将 ( x p , y p ) (x_p,y_p) (xp,yp) 右上角的点(不包括自己)单独拿出来,对于这些点,求有多少个点,满足左下角没有其他点。

我们可以 排序后使用单调栈 解决。

按照 y y y 排序,维护一个 x x x 递增的单调栈即可。再次注意,只能将 ( x p , y p ) (x_p,y_p) (xp,yp) 右上方的点放入单调栈
在这里插入图片描述原理:如果 A A A B B B 更晚入栈,那么 y a < y b y_a<y_b ya<yb ;如果同时满足 x a < x b x_a<x_b xa<xb ,那么 A A A 就在 B B B 的左下角,我们就要把 B B B 弹栈。

内测版

我们想办法把 简化版 中的方法升级。

我们能不能按照 y y y 从大到小枚举,然后维护单调栈呢?

答案是,不行。原因就在 简化版 中的黑体字部分:只将右上方的点放入单调栈

右上角是什么意思? x p < x , y p < y x_p<x,y_p<y xp<x,yp<y

我们充分利用原有的单调栈——如果 x ≤ d x\le d xd ,那么无论 x x x 怎么变动, d < x d<x d<x 的点形成的单调栈就没有多余的点;如果我们的 y y y 单减,那么 y y y 的取值范围(也就是 ( y , + ∞ ] (y,+\infty] (y,+])不断扩充,就可以在原有单调栈的基础上进行更改。

怎么做到 x ≤ d x\le d xd 中的点,在 d < x d<x d<x 的点形成的单调栈里乱来?并且两边都可以按照 y y y 重新排序?

嘿嘿嘿, T h a t ′ s    i t \tt That's\;it Thatsit 。我愿称之为 C D Q CDQ CDQ 分治

汉化破解版

我们首先对 x x x 进行排序,然后对 y y y 进行归并。

不妨把 x x x 较小的那些点叫做“ A A A 部”,其他的叫做“ B B B 部”。

我们要计算的,就只有左下角属于 A A A 、右上角属于 B B B 的矩形。

按照我们在 内测版 中的思路,我们应该将 y y y 从大到小归并。每次考虑一个点 ( x p , y p ) ∈ A (x_p,y_p)\in A (xp,yp)A 时,就把 B B B 部中所有 y p < y y_p<y yp<y 的点放到栈里。记得维护栈的单调性。

似乎每个点就有 stack . s i z e \text{stack}.size stack.size 这个多个右上角可以用?然而并不是这样的。

我们是不是忘了什么?单调栈仅仅保证了左下角没有 B B B 部内的点。 A A A 部内的点 可能在左下角!

也就是说,对于 p ∈ A , r ∈ stack p\in A,r\in\text{stack} pA,rstack ,可能存在一个点 i ∈ A i\in A iA ,满足 x p < x i < x r x_p<x_i<x_r xp<xi<xr y p < y i < y r y_p<y_i<y_r yp<yi<yr(没有相等的 x x x y y y)。单调栈只保证 i ∈ B i\in B iB 时不成立。

x i < x r x_i<x_r xi<xr c d q \tt{cdq} cdq 保证成立,可以用的 ( x i , y i ) (x_i,y_i) (xi,yi) 就满足 x p < x i , y p < y i x_p<x_i,y_p<y_i xp<xi,yp<yi ,并且 y i y_i yi 越小越好——更容易满足 y i < y r y_i<y_r yi<yr 的限制。

形式化地,求一个

λ = min ⁡ i ∈ A , x p < x i , y p < y i y i \lambda = \min_{i\in A,x_p<x_i,y_p<y_i} y_i λ=iA,xp<xi,yp<yiminyi

然后我们不可用的点,就是 ∑ i ∈ stack [ λ < y i ] \sum_{i\in\text{stack}}[\lambda <y_i] istack[λ<yi]

怎么求 λ \lambda λ?另用一个 单调栈 即可。此时 A A A 部已经是 y y y 递减了,相当于求右上角中最后加入的一个点。这个只需要维护 x x x 递减的单调栈即可。

∑ i ∈ stack [ λ < y i ] \sum_{i\in\text{stack}}[\lambda < y_i] istack[λ<yi] 又该怎么办呢?注意到栈中的元素是按照 y y y 值有序的(递减),所以可以二分查找。

每一层的时间复杂度是 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) 的,所以总复杂度为 O ( n log ⁡ 2 n ) \mathcal O(n\log^2 n) O(nlog2n)

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){ int x; scanf("%d",&x); return x; }
inline void writeint(long long x){ printf("%lld",x); }

struct Point{
	int x, y; // 一个点 
	Point(int X=0,int Y=0):x(X),y(Y){ }
	void input(){ x = readint(), y = readint(); }
	bool operator < (const Point &p){ return x < p.x; }
};

const int MaxN = 200005;
Point p[MaxN]; int n;

void input(){
	n = readint();
	for(int i=1; i<=n; ++i)
		p[i].input();
}

struct Stack{ /* stupid Stack */
	int sta[MaxN], top;
	Stack(){ top = 0; }
	void push_back(int x){ sta[++ top] = x; }
	int back()const{ return sta[top]; }
	bool empty()const{ return top == 0; }
	int size()const{ return top; }
	void pop_back(){ if(top) -- top; }
	void clear(){ top = 0; }
} lSta, rSta; /* y单减 */
int BadThing(int bound){ // 二分查找 
	int L = 0, R = rSta.top, mid;
	while(L != R){
		mid = (L+R+1)>>1;
		if(p[rSta.sta[mid]].y > bound)
			L = mid; /* 没有相等的情况 */
		else R = mid-1;
	}
	return L;
}

Point __tmp[MaxN]; long long ans;
void cdq(int left,int right){
	/* head: divide ... */
	if(left == right) return ;
	int mid = (left+right)>>1;
	cdq(left,mid), cdq(mid+1,right);
	/* body: ... and conquer */
	lSta.clear(), rSta.clear();
	for(int i=left,j=mid+1; i<=mid; ++i){
		for(; j<=right and p[j].y>p[i].y; ++j){
			/* insert p[j] */
			while(not rSta.empty() and p[j].x < p[rSta.back()].x)
				rSta.pop_back(); // 维护x单增 
			rSta.push_back(j);
		}
		/* find the "wall" */
		while(not lSta.empty() and p[lSta.back()].x < p[i].x)
			lSta.pop_back();
		/* update ans */
		ans += rSta.size();
		if(not lSta.empty())
			ans -= BadThing(p[lSta.back()].y);
		/* update lSta */
		lSta.push_back(i);
	}
	/* tail: sort it out */
	for(int i=left,j=mid+1,k=left; k<=right; ++k)
		if(i <= mid and (j > right or p[i].y > p[j].y))
			__tmp[k] = p[i ++];
		else __tmp[k] = p[j ++];
	for(int i=left; i<=right; ++i) p[i] = __tmp[i];
}
void solve(){
	/* 按照x值排序 ... */
	sort(p+1,p+n+1,Point::compareX);
	/* ... 然后将y归并 */
	cdq(1,n); // 看着很短,不是吗? 
	/* the most important part */
	writeint(ans), putchar('\n');
}

int main(){
	input(), solve();
	return 0;
}

后记

首先,感谢大佬的博客提供了思路。

其次,对自己的弱小感到一丝凄凉。吐槽一句,我查到的博客大多数都是 大佬的博客 这种风格的……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值