【LOJ2880】稻草人

题目

题目链接:https://loj.ac/problem/2880
JOI 村有一片荒地,上面竖着 \(n\) 个稻草人。任意两个稻草人的横坐标都不相同,任意两个稻草人的纵坐标都不相同。村民们每年多次在稻草人们的周围举行祭典。
有一次,JOI 村的村长听到了稻草人们的启示,计划在荒地中开垦一片田地。和启示中的一样,田地需要满足以下条件:

  • 田地的形状是边平行于坐标轴的长方形;
  • 左下角和右上角各有一个稻草人;
  • 田地的内部(不包括边界)没有稻草人。

给出每个稻草人的坐标,请你求出有多少个满足条件的田地。

思路

网上的题解都是用\(cdq+\)单调栈的优秀算法,但这个\(\color{gray}{\texttt{菜鸡stoorz}}\)实在太菜了,看不懂这些高大尚的算法。所以就写了一个\(cdq+bit+\)线段树的一种常数超级超级超级大的垃圾算法\(qwq\)然后再bzoj上T飞了,LOJ跑了倒数第一,33321 ms

首先我们发现这个题目所求其实很像三维偏序,所以考虑用\(cdq\)求解。
假设我们现在处理到的区间是\([l,r]\),我们可以对每一个点求出一个贡献区间\([l_i,r_i]\),比如说如果这个点\(i\)在左半边,那么列区间为\([l_i,r_i]\),行区间为\([x_i,mid]\)的矩形内没有任何一个点。
举个栗子,如下图

红色点的贡献区间就是红色+紫色的矩形的列的区间,蓝色点的贡献区间就是蓝色+紫色的矩形的列的区间。黄色、绿色的点的贡献区间就是该颜色的矩形的列的区间。
也就是说,\(mid\)左边的点的贡献区间的下限是该点的纵坐标,右边的点的贡献区间的上限是该点的纵坐标。
为什么要处理出这个贡献区间呢?因为我们发现,如果\(mid\)左边的点\(a\)\(mid\)右边的点\(b\)满足\(y_a\in [l_b,r_b]\)\(y_b\in [l_a,r_a]\),那么这两个点就可以构成一个矩形且这个矩形内没有其他点。例如上图,左边的蓝点和右边的黄点均满足自己的纵坐标在对方的贡献区间内,所以以蓝点为左下角,黄点为右上角的矩形就是一个合法的矩形。
那么我们现在有两个问题需要解决:

  1. 如何求出每一个点的贡献区间?
  2. 如何求出哪些点的贡献区间互相包含自己的纵坐标?

一步一步来解决。

1.如何求出每一个点的贡献区间?

按照\(cdq\)常规套路,我们把\(mid\)左右两边的点分别按纵坐标排序。
拿左边的点为例。一个点\(x\)会影响另一个点\(y\)的贡献区间,当且仅当\(x\)严格位于\(y\)的右下角。这样的话\(y\)的贡献区间的下限至少为\(x\)的纵坐标。
所以对于左边的点,我们建立一棵线段树,线段树的区间\([l,r]\)表示横坐标在\(l\sim r\)的点中,最大的纵坐标值。按纵坐标从小到大排完序后顺序枚举,如果现在枚举到点\(i\),此时线段树内已经插入了\(1\sim i-1\)的点,那么就在线段树中查询\([x_i,mid]\)中的最大值,容易发现这个最大值就是这个点的贡献区间的下限。然后再将\(i\)插入,继续往下计算。
这样我们就在\(O(n\log n)\)的时间复杂度内求出了所有区间\([l,r]\)内的点的贡献区间。需要注意,上述例子是在\(mid\)左侧的点的处理方式,\(mid\)右侧的点需要进行一些改变,请自行思考(不想码了\(qwq\))。

2.如何求出哪些点的贡献区间互相包含自己的纵坐标?

第一反应是莫队,但是莫队的复杂度是\(n\sqrt{n}\)级别的,这道题\(n\leq 2\times 10^5\),再加上\(cdq\)\(\log n\)和超级大常数,显然不行。
考虑枚举左边的点,依然按照纵坐标从小到大枚举,假设现在枚举到点\(k\),我们不难用指针维护出右半部分包含点\(k\)的点。具体的,我们将右半部分的点以及它的贡献区间复制一份到新的数组,然后将原数组按照贡献区间下限\((l)\)从小到大排序,复制后的数组按照贡献区间上限\((r)\)从小到大排序,然后维护两个指针\(i,j\)分别扫描两个数组,对于按\(l\)排序的数组,如果\(a[i].l\leq a[k].y\),那么我们就把树状数组中\(a[i].r\)的位置加一;对于按\(r\)排序的数组,如果\(b[j].r<a[k].y\),那么我们就把树状数组中\(b[j].y\)减一;这样下来,树状数组内为1的点就是贡献区间下限在\(k\)的纵坐标下方,而上限不小于\(k\)的纵坐标的点,也就是为\(1\)的点的贡献区间都包含了\(k\)
那么接下来只要满足\(k\)的贡献区间包含这些点就可以了,很简单,在树状数组中查询区间\([a[k].l,a[k].r]\)的和即可。
这样我们就在\(O(n\log n)\)的时间复杂度内求出了区间\([l,r]\)右半部分的点对左半部分的点的贡献。至此,区间\([l,r]\)的全部运算结束,接下来递归求解即可。

时间复杂度\(O(n\log^2 n)\)。常数极大。
注意要开\(\rm long\ long\)
码了这么多字累死我了qwqwq

代码

\(185\)行,约\(3.44KB\)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const ll N=200010,Inf=1e9;
ll n,tot,ans,maxn,c[N*2];

struct node
{
	ll x,y,l,r;
}a[N],b[N];

inline bool cmp1(node x,node y)
{
	return x.x<y.x;
}

inline bool cmp2(node x,node y)
{
	return x.y<y.y;
}

inline bool cmp3(node x,node y)
{
	return x.l<y.l;
}

inline bool cmp4(node x,node y)
{
	return x.r<y.r;
}

struct BIT
{
	ll c[N];
	
	inline void add(ll x,ll val)
	{
		for (;x<=maxn;x+=x&-x)
			c[x]+=val;
	}
	
	inline ll ask(ll x)
	{
		ll ans=0;
		for (;x;x-=x&-x)
			ans+=c[x];
		return ans;
	}
	
	inline void clear(ll x)
	{
		for (;x<=maxn && c[x];x+=x&-x)
			c[x]=0;
	}
}bit;

struct Treenode
{
	ll l,r,minn,maxn;
};

struct Tree
{
	Treenode tree[N*8];
	
	inline void build(ll x,ll l,ll r)
	{
		tree[x].l=l; tree[x].r=r;
		if (l==r)
		{
			tree[x].maxn=1; tree[x].minn=maxn;
			return;
		}
		ll mid=(l+r)>>1;
		build(x*2,l,mid); build(x*2+1,mid+1,r);
		pushup(x);
	}
	
	inline void pushup(ll x)
	{
		tree[x].maxn=max(tree[x*2].maxn,tree[x*2+1].maxn);
		tree[x].minn=min(tree[x*2].minn,tree[x*2+1].minn);
	}
	
	inline void update(ll x,ll k,ll val)
	{
		if (tree[x].l==k && tree[x].r==k)
		{
			tree[x].maxn=max(tree[x].maxn,val);
			tree[x].minn=min(tree[x].minn,val);
			return;
		}
		ll mid=(tree[x].l+tree[x].r)>>1;
		if (k<=mid) update(x*2,k,val);
			else update(x*2+1,k,val);
		pushup(x);
	}
	
	inline ll ask(ll x,ll l,ll r,bool type)
	{
		if (tree[x].l==l && tree[x].r==r)
			return type ? tree[x].maxn : tree[x].minn;
		ll mid=(tree[x].l+tree[x].r)>>1;
		if (r<=mid) return ask(x*2,l,r,type);
		else if (l>mid) return ask(x*2+1,l,r,type);
		else
		{
			if (type) return max(ask(x*2,l,mid,type),ask(x*2+1,mid+1,r,type));
				else return min(ask(x*2,l,mid,type),ask(x*2+1,mid+1,r,type));
		}
	}
	
	inline void clear(ll x,ll k)
	{
		tree[x].maxn=1; tree[x].minn=n;
		if (tree[x].l==tree[x].r) return;
		ll mid=(tree[x].l+tree[x].r)>>1;
		if (k<=mid) clear(x*2,k);
			else clear(x*2+1,k);
	}
}Tree;

inline void cdq(ll l,ll r)
{
	if (l==r) return;
	ll mid=(l+r)>>1;
	cdq(l,mid); sort(a+l,a+mid+1,cmp2);
	cdq(mid+1,r); sort(a+mid+1,a+r+1,cmp2);
	for (ll i=mid;i>=l;i--)
	{
		a[i].r=Tree.ask(1,a[i].x,maxn,0); a[i].l=a[i].y+1;
		Tree.update(1,a[i].x,a[i].y);
	}
	for (ll i=mid;i>=l;i--) Tree.clear(1,a[i].x);
	for (ll i=mid+1;i<=r;i++)
	{
		a[i].l=Tree.ask(1,1,a[i].x,1); a[i].r=a[i].y-1;
		Tree.update(1,a[i].x,a[i].y);
		b[i]=a[i];
	}
	for (ll i=mid+1;i<=r;i++) Tree.clear(1,a[i].x);
	
	sort(a+mid+1,a+r+1,cmp3); sort(b+mid+1,b+r+1,cmp4);
	for (ll i=mid+1,j=mid+1,k=l;k<=mid;k++)
	{
		for (;a[j].l<=a[k].y && j<=r;j++)
			bit.add(a[j].y,1);
		for (;b[i].r<a[k].y && i<=r;i++)
			bit.add(b[i].y,-1);
		ans+=bit.ask(a[k].r)-bit.ask(a[k].l-1);
	}
	for (ll i=l;i<=r;i++)
		bit.clear(a[i].y);
}

int main()
{
	scanf("%lld",&n);
	for (ll i=1;i<=n;i++)
		scanf("%lld%lld",&a[i].x,&a[i].y);
	for (ll i=1;i<=n;i++) c[i]=a[i].x;
	sort(c+1,c+1+n);
	tot=unique(c+1,c+1+n)-c-1;
	for (ll i=1;i<=n;i++)
	{
		a[i].x=lower_bound(c+1,c+1+tot,a[i].x)-c;
		maxn=max(maxn,a[i].x);
	}
	for (ll i=1;i<=n;i++) c[i]=a[i].y;
	sort(c+1,c+1+n);
	tot=unique(c+1,c+1+n)-c-1;
	for (ll i=1;i<=n;i++)
	{
		a[i].y=lower_bound(c+1,c+1+tot,a[i].y)-c;
		maxn=max(maxn,a[i].y);
	}
	Tree.build(1,1,maxn);
	sort(a+1,a+1+n,cmp1);
	cdq(1,n);
	printf("%lld",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/stoorz/p/12239987.html