一本通1752题解

题意:

给定一个 $n\times m$ 的矩形,其中矩形左下角坐标为 $(1,1)$ ,右上角坐标为 $(n,m)$ 。现在矩形中有 $p$ 个小矩形,对于每个小矩形,我们用一个四元组 $(x1,y1,x2,y2)$ 描述它,其中 $(x1,y1)$ 是它的左下角坐标,$(x2,y2)$ 是它的右上角坐标。现在请你找到一个最大的,未被小矩形覆盖的正方形,并输出它的边长。

数据范围:

对于30%的数据,p≤1000。
对于70%的数据,p≤30000。
对于100%的数据,p≤4e5,m,n≤1e6。

算法分析:

第一个想法:$O(n^3)$ 暴力 $NB$!

但是 $O(n^3)$ 肯定是过不了这题的。通过数据范围,我们可以知道题目肯定是 $O(nlogn)$ 级别的。我们考虑优化 $O(n^3)$ 的算法使其变成 $O(nlogn)$ 级别。我们必须要从左向右枚举 $i,j$ ,这一维的 $O(n^2)$ 暂时是省不掉的,但是对于从下而上的这一维 $O(n)$ ,我们可以用线段树的扫描线进行维护,时间复杂度可以优化成 $O(logn)$ 级别,至此,我们已经将原算法优化到了 $O(n^2logn)$ (但还是过不了)

接下来我们考虑继续优化。到这一步看似优无可优,实际玄机暗藏。我们不妨考虑贪心的思路,在 $n$ 这一维度上强制选择区间 $[l,r]$ 进行答案更新,随后对指针 $l$、$r$ 进行移动即可,时间复杂度 $O(nlogn)$(这也被称为双指针法)。

但是怎样具体实现呢?作者在这玄乎其玄的撤了半天,具体的思路一点也没告诉我们。~~不要急,马上来!~~首先看我的图:

 

当前我们强制选取这两条蓝线之间的区间 $[l,r]$ 更新答案。假设当前我们扫描线扫除的结果为 $t[1].ans$,我们就可以直接用 $min(r-l+1,t[1].ans)$ 更新答案。很明显当前的 $t[1].ans$ 是大于 $r-l+1$ 的,对于这种情况,我们只需要不断右移 $r$ 指针即可。

那么什么时候我们要对 $l$ 指针进行操作呢,还是看图:

 

此时的 $r$ 指针已经向后移动了,我们的扫描线扫到的 $t[1].ans$ 此时小于 $r-l+1$,再让 $r$ 一直往后移动,我们就会错过很多答案(因为答案是取 $min(t[1].ans,r-l+1)$ ),所以我们此时让 $l$ 指针右移,当 $l$ 指针右移到某个位置时,会扫到矩形出边,以至于答案变得更优。

Code Share

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+100;
int n,m,p,ans=0;
struct Tree{
    int lx,rx,mx;
    int add;
}t[N<<2];
struct node{
    int h1,h2,state;
};
vector<node> pr[N];
inline int maxx(int x,int y){
    return x>y?x:y;
}
inline int mini(int x,int y){
    return x<y?x:y;
}
void build(int p,int l,int r){
    t[p].lx=t[p].rx=t[p].mx=r-l+1;
    t[p].add=0;
    if(l==r)return ;
    int mid=l+r>>1;
    build(p<<1,l,mid);
    build(p<<1|1,mid+1,r);
}
void pushup(int p,int l,int r,int mid){
    if(t[p].add){
        t[p].lx=t[p].rx=t[p].mx=0;
        return ;
    }
    if(l==r){
        t[p].lx=t[p].rx=t[p].mx=1;
        return ;
    }
    if(t[p<<1].mx^(mid-l+1)) //mid-l+1就是左边的长度
        t[p].lx=t[p<<1].lx; //直接用左儿子的左边更新他
    else
        t[p].lx=t[p<<1].mx+t[p<<1|1].lx; //否则是中间长度加右儿子的左边
    if(t[p<<1|1].mx^(r-mid))
        t[p].rx=t[p<<1|1].rx; //直接用右儿子的右边更新 
    else
        t[p].rx=t[p<<1|1].mx+t[p<<1].rx; //否则是中间长度加左儿子的右边
    t[p].mx=max(t[p<<1].rx+t[p<<1|1].lx,max(t[p<<1].mx,t[p<<1|1].mx));
}
void modify(int p,int l,int r,int ll,int rr,int op){
    if(l>=ll && r<=rr){
        t[p].add+=op;
        pushup(p,l,r,l+r>>1);
        return ;
    }int mid=l+r>>1;
    if(ll<=mid)
        modify(p<<1,l,mid,ll,rr,op);
    if(rr>mid)
        modify(p<<1|1,mid+1,r,ll,rr,op);
    pushup(p,l,r,mid);
}
int main(){
    scanf("%d %d %d",&n,&m,&p);
    build(1,1,m);
    for(int i=1;i<=p;i++){
        int a1,b1,a2,b2;
        scanf("%d %d %d %d",&a1,&b1,&a2,&b2);
        node xk,yk;
        xk.h1=b1,xk.h2=b2,xk.state=1;
        yk.h1=b1,yk.h2=b2,yk.state=-1;
        pr[a1].push_back(xk);
        pr[a2].push_back(yk);        
    }
    int l=1,r=1;
    for(;r<=n;++r)
    {
        for(int i=0;i<pr[r].size();i++) //从0到横坐标为r,开始扫一遍
            if(pr[r][i].state==1)  //入边
                modify(1,1,m,pr[r][i].h1,pr[r][i].h2,1);
        ans=maxx(ans,mini(r-l+1,t[1].mx)); //更新答案
        for(;t[1].mx<=r-l+1;++l) //贪心思路,右移指针L
            for(int i=0;i<pr[l].size();++i)
                if(pr[l][i].state==-1) //遇到出边 
                    modify(1,1,m,pr[l][i].h1,pr[l][i].h2,-1); //删除该矩形          
    }
    printf("%d",ans);
    return 0;
}

 

posted @ 2021-10-05 22:22  青D  阅读(62)  评论(0编辑  收藏  举报