【洛谷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;
}
待到再迷茫时回头望,所有脚印会发出光芒