【洛谷4259】[Code+#3] 尋找車位(奇怪的線段樹題)
阿新 • • 發佈:2020-11-04
- 給定一張\(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\)
\(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; }