挺厉害的线段树题
m<=n,所以n<=2000,并且只有1000次修改询问,mqlogn的复杂度可以接受!
求全局?
对行(n)建一个线段树。
线段树中维护的东西,一定可以包含所有“完全包含在”这个横条中的最大正方形。
只在mid左、右的可以递归下去再取max,跨越中间的?
大小为1000的两个数组,维护区间两端的1000个位置的从左从右开始最长1的个数
线段树pushup的时候 考虑跨过mid的正方形
不妨考虑长方形 宽<=长 纵向下来的是宽,双指针l,r 横向的是长 不断往后走r 如果r-l+1>lsmin(l,r)+rsmin(l,r) 那么++l 保证宽小于等于长
(宽大于长的会在宽小的时候统计到) 相当于枚举对于r的时候,最靠上的宽小于等于长的位置l (因为ans取决于短边,也就是宽) 一定有单调性,所以l直接++即可 lsmin(l,r)+rsmin(l,r)额外用单调队列维护
子矩形?
先通过线段树把子矩形劈成logn段,就暂时消除了行宽的限制
直接做的话,对于完整的一个线段树区域,还要暴力枚举每个行中线的,就O(q*n^2logn)了
然鹅
对于每一个中线mid,设之前单调队列找到的边长是r[x][i],那么就是min(r[x][i],i-U+1)来贡献答案。(U,D是列的限制)
都是对i-U+1取min,那么r[x][i]一定就是选择最大的那一个。
所以,
每个区间再维护一个R[i],所有r[i]的max
这样保证了子矩形分到完整的区间的时候,可以直接做完return了。复杂度就能正确
对于query时往两侧都分治的区间,要再统计跨区间的最大正方形
这时最大1的长度就要和行的限制取min了。
queue用个pair存
代码:
(动态分内存)
#include<bits/stdc++.h> #define reg register int #define il inline #define numb (ch^'0') #define mid ((l+r)>>1) #define ls (x<<1) #define rs (x<<1|1) #define fi first #define se second using namespace std; typedef long long ll; il void rd(int &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } namespace Miracle{ const int N=4e6+10; int n,m,q; int *lmx[4*N],*rmx[4*N],*ans[4*N]; int Pool[14*N],*cur=Pool,*mp[N]; struct que{ int l,r; pair<int,int>q[N]; // void p_f(int L){ // while(l<=r&&q[l]<L) ++l; // } void push(int id,int v){ while(l<=r&&q[r].se>v) --r; q[++r]=make_pair(id,v); } int get(int L){ while(l<=r&&q[l].fi<L) ++l; if(l<=r) return q[l].se; else return -1; } void clear(){ l=1,r=0; } }Q1,Q2; void pushup(int x,int l,int r){ Q1.clear();Q2.clear(); int j=1; for(reg i=1;i<=m;++i){ Q1.push(i,rmx[ls][i]); Q2.push(i,lmx[rs][i]); while(i>=j&&Q1.get(j)+Q2.get(j)<i-j+1){ ++j; } ans[x][i]=i-j+1; } for(reg i=1;i<=m;++i){ ans[x][i]=max(ans[x][i],max(ans[ls][i],ans[rs][i])); lmx[x][i]=(lmx[ls][i]==mid-l+1)?lmx[ls][i]+lmx[rs][i]:lmx[ls][i]; rmx[x][i]=(rmx[rs][i]==r-mid)?rmx[rs][i]+rmx[ls][i]:rmx[rs][i]; } } void build(int x,int l,int r){ lmx[x]=cur;cur+=m+1; rmx[x]=cur;cur+=m+1; ans[x]=cur;cur+=m+1; if(l==r){ for(reg i=1;i<=m;++i){ ans[x][i]=rmx[x][i]=lmx[x][i]=mp[l][i]; } return; } build(x<<1,l,mid); build(x<<1|1,mid+1,r); pushup(x,l,r); } void upda(int x,int l,int r,int p,int t,int c){ if(l==r){ ans[x][t]=rmx[x][t]=lmx[x][t]=c; return; } if(p<=mid) upda(x<<1,l,mid,p,t,c); else upda(x<<1|1,mid+1,r,p,t,c); pushup(x,l,r); } int mx; void merge(int x,int l,int r,int L,int R,int U,int D){ Q1.clear();Q2.clear(); int j=U; for(reg i=U;i<=D;++i){ Q1.push(i,min(mid-L+1,rmx[ls][i])); Q2.push(i,min(R-mid,lmx[rs][i])); while(i>=j&&Q1.get(j)+Q2.get(j)<i-j+1){ ++j; } mx=max(mx,i-j+1); } } void query(int x,int l,int r,int L,int R,int U,int D){ if(L<=l&&r<=R){ for(reg i=U;i<=D;++i){ mx=max(mx,min(ans[x][i],i-U+1)); } return; } if(L<=mid&&mid<R) { query(x<<1,l,mid,L,R,U,D); query(x<<1|1,mid+1,r,L,R,U,D); merge(x,l,r,L,R,U,D); }else if(L<=mid){ query(x<<1,l,mid,L,R,U,D); }else{ query(x<<1|1,mid+1,r,L,R,U,D); } } int main(){ rd(n);rd(m);rd(q); Q1.clear();Q2.clear(); for(reg i=1;i<=n;++i){ mp[i]=cur;cur+=m+1; for(reg j=1;j<=m;++j){ rd(mp[i][j]); } } build(1,1,n); int x,y,l,s,r,t; int op; while(q--){ rd(op); if(op==0){ rd(x);rd(y); mp[x][y]^=1; upda(1,1,n,x,y,mp[x][y]); }else{ mx=0; rd(l);rd(s);rd(r);rd(t); query(1,1,n,l,r,s,t); printf("%d ",mx); } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* Date: 2019/2/13 21:22:39 */
总结:
抓住m<=n性质,对长的n建线段树,区间维护信息时候,处理跨域mid的最大正方形。
灵活运用单调队列。