[JZOJ4925]稻草人

博客介绍了如何利用分治策略和单调栈结合二分查找解决一道名为‘稻草人’的问题。该问题涉及在n×m的网格中寻找特定条件的平行于坐标轴的矩形。首先分析了如何计算单个关键点作为左下角时的合法矩形数量,然后详细阐述了分治过程中如何计算跨中线矩形个数,并通过归并排序优化时间复杂度至O(nlog2n)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

一个n×m的矩形网格区域,有n个关键点在上面。这些点的x坐标和y坐标互不相同。
求这个网格区域存在多少个平行于坐标轴的矩形满足:
矩形的左下端点和右上端点都是关键点
矩形的内部(不包括边界)没有任何关键点。

1n2×105,0xi,yi109


题目分析

先考虑对于一个关键点而言,我们怎么求出它作为左下角时所有的合法矩形。显然我们可以计算出其右上方所有关键点x递增y递减的单调栈,栈上的点数就是该点作为左下角的贡献。
那么现在我们有很多个关键点都要计算,怎么办呢?考虑使用分治。我们按照x坐标分治,每次计算所有跨过x=mid这条直线(左端点x坐标小于等于mid,右端点x坐标大于mid)的合法矩形的个数,然后递归处理xmidx>mid的点。
怎么计算这个跨中线矩形数量呢?我们将所有在当前分治范围内的点按照y坐标从大到小排序,然后依次插入关键点。可以发现,我们对于所有x>mid维护y递减x递增的单调栈,对于左边新加入的点,右端只有在栈上的点才可以和该点组成合法矩形。但是即便是在栈上,也不一定可以,因为左端该点上方的点也有可能限制了该点的延伸。因此我们对左边再维护一个y递减x递减的单调栈,那么我往栈中加入当前点,弹掉不合法元素之后,前一个点便是限制我的那个点,然后右端能够和当前点组成矩形的点,其y坐标一定小于这个限制点,这个在右边二分查找就好了。
当然,为了让程序跑得更快,我们在分治过程中的排序可以使用归并排序,这样的话我们就要先分治再计算当前区间答案。
时间复杂度O(nlog2n)


代码实现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cctype>

using namespace std;

inline int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int X=1000000000;
const int N=200050;

struct P
{
    int x,y;

    P(int x_=0,int y_=0){x=x_,y=y_;}
}scs[N];

bool cmp1(P a,P b){return a.x<b.x;}
bool cmp2(P a,P b){return a.y>b.y;}

long long ans;
P stack[2][N];
int top[2];
P tmp[N];
int n;

void divide(int l,int r)
{
    if (l==r) return;
    int mid=l+r>>1,x0=scs[mid].x;
    divide(l,mid),divide(mid+1,r);
    int cnt=0,cur1=l,cur2=mid+1;
    for (;cur1<=mid||cur2<=r;)
        if (cur2>r||cur1<=mid&&cmp2(scs[cur1],scs[cur2])) tmp[++cnt]=scs[cur1++];
        else tmp[++cnt]=scs[cur2++];
    memcpy(scs+l,tmp+1,cnt*sizeof(P));
    stack[0][top[0]=0]=P(X+1,X+1),stack[1][top[1]=0]=P(-X-1,X+2);
    for (int i=l;i<=r;i++)
        if (scs[i].x<=x0)
        {
            for (;top[0]&&stack[0][top[0]].x<scs[i].x;top[0]--);
            int y0=stack[0][top[0]].y;
            stack[0][++top[0]]=scs[i];
            int L=0,R=top[1],Mid,Ret=-1;
            while (L<=R)
            {
                Mid=L+R>>1;
                if (stack[1][Mid].y>y0) L=(Ret=Mid)+1;
                else R=Mid-1;
            }
            ans+=top[1]-Ret;
        }
        else
        {
            for (;top[1]&&stack[1][top[1]].x>scs[i].x;top[1]--);
            stack[1][++top[1]]=scs[i];
        }
}

int main()
{
    freopen("scarecrows.in","r",stdin),freopen("scarecrows.out","w",stdout);
    n=read();
    for (int i=1;i<=n;i++) scs[i].x=read(),scs[i].y=read();
    sort(scs+1,scs+1+n,cmp1);
    ans=0,divide(1,n);
    printf("%lld\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值