纪念碑

题目大意

在N*M的网格中,有P个矩形建筑,求一个最大边长的正方形,使得网格中能找到一个放置正方形的地方,不会与建筑重合。
N,M<=10^6,P<=40000。

扫描线

如果有两条在x轴上的扫描线l与r,表示l~r-1之间可以放边长为r-l的正方形。
期望在l与r间放一个边长为r-l+1的矩形。
那么假如我们能在两条扫描线间找到最大空隙ms。
如果r-l+1<=ms,那么可以放,接下来r+1。
如果r-l+1>ms,便不能放,因此l+1。由于l~r-1之间可以放边长为r-l的正方形,l+1~r-1之间便可以放边长为r-l-1的正方形。所以r不用变。

错误的线段树

现在问题就是如何求空隙。
显然可以使用线段树,维护最大连续0。每个位置的数代表被几个障碍包含。
当r+1时,加入左边界在x=r上的矩形。
当l+1时,删除右边界在x=l-1上的矩形。
如何维护最大连续0?
我们可以维护num,lnum,rnum分别表示最大连续0,左起最大连续0,右起最大连续0。
显然

tree[p].num=max(tree[p*2].rnum+tree[p*2+1].lnum,max(tree[p*2].num,tree[p*2+1].num));
        tree[p].lnum=tree[p*2].lnum;
        if (tree[p*2].lnum==mid-l+1) tree[p].lnum+=tree[p*2+1].lnum;
        tree[p].rnum=tree[p*2+1].rnum;
        if (tree[p*2+1].rnum==r-mid) tree[p].rnum+=tree[p*2].rnum;

注意到这道题要支持区间添加与区间删减,且删减区间与添加区间一一对应,再加上一个区间只要有add标记那么最大连续0一定为0,所以我们可以不下传标记,打一个“错误”的线段树。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=1000000+10,maxp=400000+10;
struct dong{
    int x1,x2,y1,y2;
};
struct suan{
    int num,lnum,rnum,add;
};
dong a[maxp];
suan tree[maxn*10];
int h[maxn],next[maxn*2],go[maxn*2];
int i,j,k,l,r,t,n,m,p,tot,ans,ms;
void inse(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void build(int p,int l,int r){
    if (l==r){
        tree[p].num=tree[p].lnum=tree[p].rnum=1;
        return;
    }
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
    tree[p].num=tree[p].lnum=tree[p].rnum=tree[p*2].num+tree[p*2+1].num;
}
void add(int p,int l,int r,int a,int b){
    if (l==a&&r==b){
        tree[p].add++;
        tree[p].num=tree[p].lnum=tree[p].rnum=0;
        return;
    }
    int mid=(l+r)/2;
    if (b<=mid) add(p*2,l,mid,a,b);
    else if (a>mid) add(p*2+1,mid+1,r,a,b);
    else{
        add(p*2,l,mid,a,mid);
        add(p*2+1,mid+1,r,mid+1,b);
    }
    if (!tree[p].add){
        tree[p].num=max(tree[p*2].rnum+tree[p*2+1].lnum,max(tree[p*2].num,tree[p*2+1].num));
        tree[p].lnum=tree[p*2].lnum;
        if (tree[p*2].lnum==mid-l+1) tree[p].lnum+=tree[p*2+1].lnum;
        tree[p].rnum=tree[p*2+1].rnum;
        if (tree[p*2+1].rnum==r-mid) tree[p].rnum+=tree[p*2].rnum;
    }
}
void sub(int p,int l,int r,int a,int b){
    int mid=(l+r)/2;
    if (l==a&&r==b){
        tree[p].add--;
        if (!tree[p].add){
            if (l!=r){
                tree[p].num=max(tree[p*2].rnum+tree[p*2+1].lnum,max(tree[p*2].num,tree[p*2+1].num));
                tree[p].lnum=tree[p*2].lnum;
                if (tree[p*2].lnum==mid-l+1) tree[p].lnum+=tree[p*2+1].lnum;
                tree[p].rnum=tree[p*2+1].rnum;
                if (tree[p*2+1].rnum==r-mid) tree[p].rnum+=tree[p*2].rnum;
            }
            else tree[p].num=tree[p].lnum=tree[p].rnum=1;
        }
        return;
    }
    if (b<=mid) sub(p*2,l,mid,a,b);
    else if (a>mid) sub(p*2+1,mid+1,r,a,b);
    else{
        sub(p*2,l,mid,a,mid);
        sub(p*2+1,mid+1,r,mid+1,b);
    }
    if (!tree[p].add){
        tree[p].num=max(tree[p*2].rnum+tree[p*2+1].lnum,max(tree[p*2].num,tree[p*2+1].num));
        tree[p].lnum=tree[p*2].lnum;
        if (tree[p*2].lnum==mid-l+1) tree[p].lnum+=tree[p*2+1].lnum;
        tree[p].rnum=tree[p*2+1].rnum;
        if (tree[p*2+1].rnum==r-mid) tree[p].rnum+=tree[p*2].rnum;
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&p);
    fo(i,1,p){
        scanf("%d%d%d%d",&a[i].x1,&a[i].y1,&a[i].x2,&a[i].y2);
        inse(a[i].x1,i);
        inse(a[i].x2,i);
    }
    build(1,1,m);
    l=r=1;
    t=h[1];
    while (t){
        add(1,1,m,a[go[t]].y1,a[go[t]].y2);
        t=next[t];
    }
    while (r<=n){
        ms=tree[1].num;
        if (r-l+1>ms){
            l++;
            t=h[l-1];
            while (t){
                if (a[go[t]].x2==l-1) sub(1,1,m,a[go[t]].y1,a[go[t]].y2);
                t=next[t];
            }
        }
        else{
            ans=max(ans,r-l+1);
            r++;
            t=h[r];
            while (t){
                if (a[go[t]].x1==r) add(1,1,m,a[go[t]].y1,a[go[t]].y2);
                t=next[t];
            }
        }
    }
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值