zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    二元函数求一定区间上的最大值问题
    LOF异常检测算法实现
    失效项目总代码汇总
    layui弹出层layer.open 中的content问题
    分治算法基本原理和实践
    Android 文件存储浅析
    详解 CmProcess 跨进程通信的实现
    View Animation 运行原理解析
    margin-top失效及解决办法
    vue自定义移动端touch事件之点击、滑动、长按事件
  • 原文地址:https://www.cnblogs.com/stoorz/p/12239987.html
Copyright © 2011-2022 走看看