BZOJ 4237稻草人【分治】

1 篇文章 0 订阅

Description

JOI村有一片荒地,上面竖着N个稻草人,村民们每年多次在稻草人们的周围举行祭典。

有一次,JOI村的村长听到了稻草人们的启示,计划在荒地中开垦一片田地。和启示中的一样,田地需要满足以下条件:

田地的形状是边平行于坐标轴的长方形;

左下角和右上角各有一个稻草人;

田地的内部(不包括边界)没有稻草人。

给出每个稻草人的坐标,请你求出有多少遵从启示的田地的个数

题解

分治。由于是两维的,可以使其中一维有序来降低思考难度。

先按 x 从大到小排序,考虑将元素分成左右两半,考虑如何合并这两半的答案,考虑将两个区间分别按照y排序,枚举左下角的点。枚举一个左下角的端点,在右上角肯定要比它高,那就把所有 y 值比它大的点都加入到一个栈中,维护单调栈,显然,这一定是一个x做坐标单调升的栈。对于右边的点,也做类似的处理,可以发现,左边是一个单调降的栈,而对于每个左下角的点,它的右上角的 y 值不能大于它在栈中的前一个点的y值,而因为 y 是有序的,所以直接在右边的栈中二分就可以了。

时间复杂度:O(nlog2n)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 200006
#define LL long long
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int _read(){
    char ch=nc();int sum=0;
    while(!(ch>='0'&&ch<='9'))ch=nc();
    while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
    return sum;
}
struct data{
    int x,y;
    bool operator <(const data&b)const{return x<b.x;}
}a[maxn],stack[2][maxn];
int n,top[2];
LL ans;
int find(int x){
    int l=1,r=top[1];
    while(l<=r){
        int mid=(l+r)>>1;
        if(stack[1][mid].y>x)l=mid+1;
                        else r=mid-1;
    }
    return l;
}
bool cmp(data x,data y){return x.y>y.y;}
void work(int l,int r){
    if(l>=r)return;
    int mid=(l+r)>>1;
    work(l,mid);work(mid+1,r);
    top[0]=top[1]=0;int j=mid+1;
    for(int i=l;i<=mid;i++){
        while(j<=r&&a[j].y>a[i].y){
            while(top[1]&&stack[1][top[1]].x>a[j].x)top[1]--;
            stack[1][++top[1]]=a[j++];
        }
        while(top[0]&&stack[0][top[0]].x<a[i].x)top[0]--;
        stack[0][++top[0]]=a[i];
        if(top[0]>1)ans+=top[1]-find(stack[0][top[0]-1].y)+1;
               else ans+=top[1];
    }
    sort(a+l,a+1+r,cmp);
}
int main(){
    freopen("strawman.in","r",stdin);
    freopen("strawman.out","w",stdout);
    n=_read();
    for(int i=1;i<=n;i++)a[i].x=_read(),a[i].y=_read();
    sort(a+1,a+1+n);
    work(1,n);
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值