【线段树】【枚举】[Code+#3]寻找车位

18 篇文章 0 订阅
11 篇文章 0 订阅

分析:

没见过这么暴力的数据结构题。

其实方法很简单:
首先,很容易想到一种暴力:枚举一行作为上边界,然后从左到右依次考虑每一列作右边界的情况:假设当前枚举的行为 i i i,列为 j j j,每一列 j j j i i i行开始,其下边的连续为1的最大长度为 l e n j len_j lenj
很容易发现,若对于第j列,其优的左边界k必然满足: l e n k ≥ j − k + 1 len_k\geq j-k+1 lenkjk+1,否则的话,就可以把 k = k + 1 k=k+1 k=k+1,其答案必然不会更劣。

同时,也很容易发现,最优的左边界的len一定组成了一个单调递增的序列。所以可以单调队列维护这个玩意,就可以在O(m)的复杂度内,统计完上边界在某一行的所有情况。

然后,由于询问时的矩阵限制,以及修改操作,必须对这个算法进行优化。

每一列都建一颗线段树,表示:以当前列为右边界的矩形,行的范围在[l,r]区间内,可以达到的最大空正方形。

然后,考虑如何合并两个线段:首先,两个儿子内部的不用说,只用在两侧的答案中各取一个max即可。然后就是考虑合并两个线段后新产生的正方形。

那么这个正方形一定过 m i d mid mid这一行。所以可以用O(m)的复杂度,依次从左到右考虑每一列作为右端点的情况。
没错,这就是上面的暴力。
并且,这个题线段树的合并操作是m个线段树同时合并的!

然后询问的时候就很容易控制边界矩形的影响了。询问时有个很厉害的技巧:直接搞一个空的线段出来,然后依次把它和每个划分出来的线段合并。可以很大程度上节约代码量。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 4000010
using namespace std;
int n,m;
struct matrix{
	int f[MAXN*4];
	int *operator [](int x){
		return f+x*m;	
	}
}mp,rt,lf,v;
int len[MAXN*4];
void merge(int id,int idl,int idr){
	static int q1[MAXN],q2[MAXN];
	int hl=1,tl=0;
	int hr=1,tr=0;
	for(int i=1,j=1;i<=m;i++){
		while(hl<=tl&&lf[idr][q1[tl]]>lf[idr][i]) tl--;
		q1[++tl]=i;
		while(hr<=tr&&rt[idl][q2[tr]]>rt[idl][i]) tr--;
		q2[++tr]=i;
		while(hl<=tl&&hr<=tr&&lf[idr][q1[hl]]+rt[idl][q2[hr]]<(i-j+1)){
			while(q1[hl]<=j&&hl<=tl) hl++;
			while(q2[hr]<=j&&hr<=tr) hr++;
			j++;
		}
		v[id][i]=max(i-j+1,max(v[idl][i],v[idr][i]));
	}
	for(int i=1;i<=m;i++){
		if(lf[idl][i]==len[idl])
			lf[id][i]=len[idl]+lf[idr][i];
		else
			lf[id][i]=lf[idl][i];
		if(rt[idr][i]==len[idr])
			rt[id][i]=len[idr]+rt[idl][i];
		else
			rt[id][i]=rt[idr][i];
	}
}
int merge(int idl,int idr,int l,int r){
	static int q1[MAXN],q2[MAXN];
	int hl=1,tl=0;
	int hr=1,tr=0;
	int res=0;
	for(int i=l,j=l;i<=r;i++){
		while(hl<=tl&&lf[idr][q1[tl]]>lf[idr][i]) tl--;
		q1[++tl]=i;
		while(hr<=tr&&rt[idl][q2[tr]]>rt[idl][i]) tr--;
		q2[++tr]=i;
		while(hl<=tl&&hr<=tr&&lf[idr][q1[hl]]+rt[idl][q2[hr]]<(i-j+1)){
			while(q1[hl]<=j&&hl<=tl) hl++;
			while(q2[hr]<=j&&hr<=tr) hr++;
			j++;
		}
		res=max(res,i-j+1);
	}
	for(int i=l;i<=r;i++){
		if(lf[idl][i]==len[idl])
			lf[idl][i]=len[idl]+lf[idr][i];
		else
			lf[idl][i]=lf[idl][i];
		if(rt[idr][i]==len[idr])
			rt[idl][i]=len[idr]+rt[idl][i];
		else
			rt[idl][i]=rt[idr][i];
	}
	len[idl]+=len[idr];
	return res;
}
void modify(int id,int l,int r,int px,int py){
	if(l==r){
		v[id][py]=lf[id][py]=rt[id][py]=mp[px][py];	
		return ;
	}
	int mid=(l+r)>>1;
	if(px<=mid)
		modify(id<<1,l,mid,px,py);
	else
		modify(id<<1|1,mid+1,r,px,py);
	merge(id,id<<1,id<<1|1);
}
int query(int id,int l,int r,int dl,int dr,int xl,int xr){
	if(dl<=l&&dr>=r){
		int res=merge(0,id,xl,xr);
		for(int i=xl;i<=xr;i++)
			res=max(res,min(v[id][i],i-xl+1));
		return res;	
	}
	int res=0,mid=(l+r)>>1;
	if(dl<=mid)
		res=max(res,query(id<<1,l,mid,dl,dr,xl,xr));
	if(dr>mid)
		res=max(res,query(id<<1|1,mid+1,r,dl,dr,xl,xr));
	return res;
}
int cquery(int dl,int dr,int xl,int xr){
	for(int i=1;i<=m;i++)
		v[0][i]=lf[0][i]=rt[0][i]=0;
	len[0]=0;
	return query(1,1,n,dl,dr,xl,xr);
}
void build(int id,int l,int r){
	if(l==r){
		for(int i=1;i<=m;i++)
			lf[id][i]=rt[id][i]=v[id][i]=mp[l][i];
		len[id]=1;
		return ;	
	}
	int mid=(l+r)>>1;
	build(id<<1,l,mid);
	build(id<<1|1,mid+1,r);
	len[id]=len[id<<1]+len[id<<1|1];
	merge(id,id<<1,id<<1|1);
}
int main(){
	int q;
	SF("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			SF("%d",&mp[i][j]);
	build(1,1,n);
	int tag,x,y,l,s,r,t;
	for(int i=1;i<=q;i++){
		SF("%d",&tag);
		if(tag==0){
			SF("%d%d",&x,&y);
			mp[x][y]^=1;
			modify(1,1,n,x,y);	
		}
		else{
			SF("%d%d%d%d",&l,&s,&r,&t);
			PF("%d\n",cquery(l,r,s,t));	
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值