把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷4259】[Code+#3] 寻找车位(奇怪的线段树题)

点此看题面

  • 给定一张\(n\times m\)\(01\)矩形,支持两种操作:
    • 单点修改。
    • 询问一个子矩形内最大全\(1\)正方形边长。
  • 操作次数\(\le2\times10^3,n\times m\le4\times10^6(n\ge m)\)

奇怪的线段树

考虑\(n\times m\le4\times10^6\)说明\(m\le2\times10^3\)

既然\(m,q\)都很小,那么我们完全可以去构思一个\(O(mqlogn)\)的做法。

\(1\)正方形的常规解法是单调队列,现在询问子矩形,我们可以使用一个以为下标的线段树维护。

具体地,线段树上每个节点开三个长度为\(m\)的数组\(Up,Dn,V\),分别表示这些行内每一列上方连续\(1\)的个数、下方连续\(1\)的个数、以这列为右端点的答案

\(PushUp\)的时候\(Up\)\(Dn\)的信息是非常容易上传的,\(V\)的上传就像先前提到的那样,采用单调队列即可,相信大家都会(实在不会可以看代码,有注释)。

奇怪的数组

注意这题只给出了\(n\times m\)的大小,数组不能乱开。

考虑开\(vector\),不知道是不是我实现不够优秀,尽管是\(1000MB\)的内存,依旧华丽地\(MLE\)了。。。

所以我们采用指针,把数组开成这个样子:

struct Array {int v[NM<<2];I int* operator [] (CI x) {return v+x*m;}};

然后就能和平时一样地使用数组了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define NM 4000000
#define M 2000
using namespace std;
int n,m;struct Array {int v[NM<<2];I int* operator [] (CI x) {return v+x*m;}}a;//奇怪的数组
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}F;
class SegmentTree
{
	private:
		#define PT CI l=1,CI r=n,CI rt=1
		#define LT l,mid,rt<<1
		#define RT mid+1,r,rt<<1|1
		#define PU(x) Merge(rt,rt<<1,rt<<1|1,mid-l+1,r-mid)//上传信息就是合并两个子区间
		int q1[M+5],q2[M+5];Array Up,Dn,V;
		I void Merge(CI rt,CI A,CI B,CI L,CI R)//合并A,B信息到rt
		{
			RI i,j,H1=1,T1=0,H2=1,T2=0;for(i=j=1;i<=m;++i)//枚举右端点
			{
				W(H1<=T1&&Dn[A][q1[T1]]>Dn[A][i]) --T1;q1[++T1]=i;//第一个单调队列,维护上面的行下方连续的1的个数递减
				W(H2<=T2&&Up[B][q2[T2]]>Up[B][i]) --T2;q2[++T2]=i;//第二个单调队列,维护下面的行上方连续的1的个数递减
				W(i-j+1>Up[B][q2[H2]]+Dn[A][q1[H1]]) q1[H1]^j||++H1,q2[H2]^j||++H2,++j;//如果列数大于行的答案,移动左端点
				V[rt][i]=max(max(V[A][i],V[B][i]),i-j+1);//答案是两个子矩阵答案与当前求出答案的较大值
			}
			for(i=1;i<=m;++i)//上传Up和Dn信息,注意不能并到上面(求解询问时rt=A,会影响到A的值)
				Up[rt][i]=Up[A][i]+(Up[A][i]^L?0:Up[B][i]),//如果是满的,下面的行可以接上
				Dn[rt][i]=Dn[B][i]+(Dn[B][i]^R?0:Dn[A][i]);//如果是满的,上面的行可以接上
		}
	public:
		I void Build(PT)//建树
		{
			if(l==r) {for(RI i=1;i<=m;++i) Up[rt][i]=Dn[rt][i]=V[rt][i]=a[l][i];return;}
			RI mid=l+r>>1;Build(LT),Build(RT),PU(rt);
		}
		I void U(CI x,CI y,CI v,PT)//单点修改
		{
			if(l==r) return (void)(Up[rt][y]=Dn[rt][y]=V[rt][y]=v);
			RI mid=l+r>>1;x<=mid?U(x,y,v,LT):U(x,y,v,RT),PU(rt);
		}
		I void G(CI L,CI R,PT)//找到询问的那些行
		{
			if(L<=l&&r<=R) return Merge(0,0,rt,l-L,r-l+1);//总是先访问左边,依次得到已询问行的总大小
			RI mid=l+r>>1;L<=mid&&(G(L,R,LT),0),R>mid&&(G(L,R,RT),0);
		}
		I int Q(CI x,CI y,CI u,CI v)//询问
		{
			RI i,t=0;for(i=1;i<=m;++i) Up[0][i]=Dn[0][i]=V[0][i]=0;
			for(G(x,u),i=y;i<=v;++i) t=max(t,min(V[0][i],i-y+1));return t;//枚举询问每一列,注意不能超出询问左端点
		}
}S;
int main()
{
	RI Qt,i,j,x,y;for(F.read(n,m,Qt),i=1;i<=n;++i) for(j=1;j<=m;++j) F.read(a[i][j]);
	RI op,u,v;S.Build();W(Qt--) F.read(op,x,y),
		op?(F.read(u,v),printf("%d\n",S.Q(x,y,u,v))):(S.U(x,y,a[x][y]^=1),0);return 0;
}
posted @ 2020-11-04 19:48  TheLostWeak  阅读(82)  评论(0编辑  收藏  举报