1. 程式人生 > 實用技巧 >【洛谷4259】[Code+#3] 尋找車位(奇怪的線段樹題)

【洛谷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;
}