纪念碑 题解

题面

题意:在一个有很多矩形覆盖的区域(n,m)内,找到未被覆盖区域中边长最大的正方形。

显然,扫描线。

考虑用两条扫描线 ( x = l , x = r ) (x=l,x=r) (x=l,x=r)维护 ( l , 0 ) , ( r , m ) (l,0),(r,m) (l,0),(r,m)这个大矩形区域内纵坐标未被覆盖的最大长度。
如图所示:

soFtWF.png

x = l , x = r x=l,x=r x=l,x=r两条线段即为要维护的扫描线。

根据肉眼观察:

扫描线与区域范围所成的大矩形中纵坐标未被覆盖的最大长度就是蓝紫色标识的范围;

在这种情况下大矩形中最大正方形边长就是 l − r − 1 l-r-1 lr1(题上给的坐标是小方块的位置,所以横向距离是 ( r − l − 1 ) (r-l-1) (rl1))。

考虑大致思路:

  • 用线段树维护两条扫描线之间纵坐标最长未被矩形覆盖的长度smax。

  • 在移动两条扫描线时最大正方形边长 a n s = m a x ( m a x , m i n ( r − l − 1 , s m a x ) ) ans=max(max,min(r-l-1,smax)) ans=max(max,min(rl1,smax))

  • 输出答案即可。

似乎挺容易。

那么考虑具体操作:

平平无奇的建树:

用线段树维护4个值 l m a x , r m a x , s m a x , s u m lmax,rmax,smax,sum lmax,rmax,smax,sum。分别表示区间从左端点向右延伸能覆盖的最大长度 ( l m a x ) (lmax) (lmax),区间从右端点向左延伸能覆盖的最大长度 ( r m a x ) (rmax) (rmax),区间内最长连续覆盖长度 ( s m a x ) (smax) (smax),区间被覆盖次数 ( s u m ) (sum) (sum)

这些变量用法下面会提:

struct node
{
	int lmax,rmax,smax;
	int sum;
}tr[N<<2];

如何维护区间最长未被矩形覆盖的长度(如何进行pushup)?
下面分情况讨论:
  • 最长长度完全在左区间或右区间:

$ tree[p].smax=max( tr[p<<1].smax , tr[p<<1|1].smax ) $

  • 最长长度所在区间跨过区间中点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BX6XWNYw-1612343693021)(https://s3.ax1x.com/2021/01/22/sIRlMq.png)]

则最长长度就是左区间的右端点向左延伸的最大长度加上右区间的左端点向右延伸的最大长度(难搞,建议直接看下面的式子)

$ tree[p].smax= tr[p<<1].rmax + tr[p<<1|1].lmax $

  • 则一般情况即为:

$ tree[p].smax=max( tr[p<<1].rmax+tr[p<<1|1].lmax , max( tr[p<<1].smax , tr[p<<1|1].smax ) )$

  • 维护 l m a x , r m a x lmax,rmax lmax,rmax:

如果区间左半边未被完全覆盖,那么这个区间左端延伸最大值即为左区间的左端延伸最大值:

t r [ p ] . l m a x = t r [ p < < 1 ] . l m a x tr[p].lmax=tr[p<<1].lmax tr[p].lmax=tr[p<<1].lmax

反之,这个区间左端延伸最大值即为左区间的长度加上右区间左端延伸最大值:

t r [ p ] . l m a x = t r [ p < < 1 ] . l m a x + t r [ p < < 1 ∣ 1 ] . l m a x tr[p].lmax=tr[p<<1].lmax+tr[p<<1|1].lmax tr[p].lmax=tr[p<<1].lmax+tr[p<<11].lmax

维护 r m a x rmax rmax同理,不讲了

inline void pushup(int p,int l,int r)
{
	if(tr[p].sum)//这个区间被覆盖了,lmax,rmax,smax显然是0
	{
		tr[p]=(node){0,0,0,tr[p].sum};
		return ;
	}
	if(l==r)//特判叶子节点
	{
		tr[p]=(node){1,1,1,0};
		return ;
	}
	int mid=(l+r)>>1;
	int d1=mid-l+1;
	int d2=r-mid;
	tr[p].lmax= tr[p<<1].smax==d1?d1+tr[p<<1|1].lmax:tr[p<<1].lmax;//维护lmax,rmax,smax
	tr[p].rmax= tr[p<<1|1].smax==d2?d2+tr[p<<1].rmax:tr[p<<1|1].rmax;
	tr[p].smax= max(tr[p<<1].rmax+tr[p<<1|1].lmax,max(tr[p<<1].smax,tr[p<<1|1].smax));
}
如何进行pushdown?

因为这道题查询的是总区间的最长未覆盖长度(即为根节点信息)。

所以没有必要进行 p u s h d o w n pushdown pushdown操作!

扫描线具体操作?
  • 先把所有矩形的左右边存起来(扫描线基础操作)。

  • 设置左右两条扫描线,先移动 x = r x=r x=r,当区间最长连续段 $ < $ 扫描线距离 时向左移动 x = l x=l x=l(这样才满足最优情况)。

  • 扫描线移动期间不断记录答案:

$ ans=max(ans,min(tr[1].smax,r-l+1))$

最后输出 a n s ans ans即可。
CODE:
#include<cstdio>
#include<vector>
#include<memory.h>
#include<iostream>
#include<algorithm>
#define N 1300100
#define LL long long 
using namespace std;

int n,m,op,ans;
vector<int>py[N],qy[N],t[N];//存边用的
struct node
{
	int lmax,rmax,smax;
	int sum;
}tr[N<<2];

inline int min(int a,int b)
{
	return a>b?b:a;
}

inline int max(int a,int b)
{
	return a>b?a:b;
}

inline int qr()
{
	char a=0;int x=0,w=1;
	while(a<'0'||a>'9'){if(a=='-')w=-1;a=getchar();}
	while(a<='9'&&a>='0'){x=(x<<3)+(x<<1)+(a^48);a=getchar();}
	return x*w;
}

void build(int p,int l,int r)
{
	int d=r-l+1;
	tr[p]=(node){d,d,d,0};//刚开始所有区间都未被覆盖
	if(l==r)
		return ;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}

inline void pushup(int p,int l,int r)
{
	if(tr[p].sum)//这个区间被覆盖了,lmax,rmax,smax显然是0
	{
		tr[p]=(node){0,0,0,tr[p].sum};
		return ;
	}
	if(l==r)//特判叶子节点
	{
		tr[p]=(node){1,1,1,0};
		return ;
	}
	int mid=(l+r)>>1;
	int d1=mid-l+1;
	int d2=r-mid;
	tr[p].lmax= tr[p<<1].smax==d1?d1+tr[p<<1|1].lmax:tr[p<<1].lmax;//维护lmax,rmax,smax
	tr[p].rmax= tr[p<<1|1].smax==d2?d2+tr[p<<1].rmax:tr[p<<1|1].rmax;
	tr[p].smax= max(tr[p<<1].rmax+tr[p<<1|1].lmax,max(tr[p<<1].smax,tr[p<<1|1].smax));
}

void modify(int p,int l,int r,int L,int R,int x)//区间修改
{
	if(L<=l&&r<=R)
	{
		tr[p].sum+=x;
		pushup(p,l,r);
		return ;
	}
	int mid=(l+r)>>1;
	if(L<=mid)
		modify(p<<1,l,mid,L,R,x);
	if(R>mid)
		modify(p<<1|1,mid+1,r,L,R,x);
	pushup(p,l,r);
}

int main()
{
	freopen("square.in","r",stdin);
	freopen("square.out","w",stdout);
	n=qr();
	m=qr();
	op=qr();
	for(register int i=1;i<=op;i++)
	{
		int x1=qr();
		int y1=qr();
		int x2=qr();
		int y2=qr();
		py[x1].push_back(y1);//存线
		qy[x1].push_back(y2);
		t[x1].push_back(1);

		py[x2].push_back(y1);
		qy[x2].push_back(y2);
		t[x2].push_back(-1);
	}
	build(1,1,m);
	int l=1;
	for(register int r=1;r<=n;r++)//不断移动右扫描线
	{
		int tot=py[r].size();
		for(register int i=0;i<tot;i++)
			if(t[r][i]==1)
				modify(1,1,m,py[r][i],qy[r][i],1);//将覆盖右扫描线的线段加到线段树上
		ans=max(ans,min(tr[1].smax,r-l+1));//更新答案
		while(tr[1].smax<r-l+1)//为找到最优情况移动左扫描线
		{
			int toa=py[l].size();
			for(register int i=0;i<toa;i++)//去除矩形覆盖
				if(t[l][i]==-1)
					modify(1,1,m,py[l][i],qy[l][i],-1);
			l++;
			ans=max(ans,min(tr[1].smax,r-l+1));
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值