最大子矩形问题

最大子矩形问题

题面:

\(n\times m\) 平面上给出 \(K\) 个关键点,求面积最大的矩形满足其内部(边界不算)没有关键点。

  • 以下记这个平面为 \(n\times m\) 的区域为 \((0,0)\)\((n,m)\)
  • 文中提到的“上下左右”方向均以平面直角坐标系为参考,即 \((>x,y)\) 为右,\((x,>y)\) 为上。

极大化思想:

  • 称一个矩形合法,当且仅当其内部没有关键点。
  • 称一个矩形为极大子矩形,当且仅当其合法且不存在更大的包含它的合法矩形。
  • 最大子矩形为所有合法子矩形中最大的一个。

显然极大子矩形满足四个边界上均有关键点或到达平面边界

求解最大子矩形问题的核心在于极大化思想,即显然有最大子矩形一定是一个极大子矩形中最大的那个

两种常用算法:

根据上网搜索和王知昆《浅谈用极大化思想解决最大子矩形问题》得知两种复杂度为 \(O(K^2)\)\(O(nm)\) 的做法。

\(O(K^2)\) 扫描法:

将所有极大子矩形分为三类:左边界上有关键点的矩形,右边界上有关键点的矩形,左右都是平面边界的矩形。

考虑对于左边界有关键点的矩形(右边界对称是同理的):
枚举左边界上的点 \((x,y)\) 将其右边的点从左到右顺次加入,每次加入时将当前极大子矩形计入答案并更新可行上下界,直到到达平面边界。

更具体的算法流程为:

  • 枚举点 \((x,y)\),寻找左边界上是这个关键点的极大子矩形。实时维护当前可行上下界 \(l,r\) 初始 \(l=0,r=m\)
  • 从左到右枚举每个 \((x,y)\) 右边的点 \((a,b)\)
    • \(b\not\in[l,r]\) 则跳过,否则将 \((a-x)(r-l)\) 计入答案。
    • 如果没有被跳过:若 \(b>y\)\(r=\min(r,b)\);若 \(b<y\)\(l=\max(l,b)\);若 \(b=y\) 则直接结束 \((x,y)\) 点的寻找。
  • 到达边界并将 \((n-x)(r-l)\) 计入答案然后结束 \((x,y)\) 点的寻找。

对于两边都是平面边界的矩形:

  • 将所有点按照 \(y\) 坐标排序,取出相邻两个点 \((x_1,y_1)(x_2,y_2)\),将 \(n\times |y_2-y_1|\) 计入答案。(还有第一个和最后一个点与边界形成的矩形也要算

例题 P1578 奶牛浴场

\(O(nm)\) 悬线法:

  • 称除两端点外不覆盖任何关键点的竖直线段为有效竖线。
  • 称上端点覆盖关键点或在平面上界的有效竖线为悬线。

显然有每一个极大子矩形都可以由一条悬线尽可能向左右移动得到

每条悬线由其底部的点唯一确定,即对于一个点找到其上第一个关键点(或到达边界),这两点之间的线段就是一条悬线,所有点这样做可以取遍所有悬线。这可以通过维护每个点上方第一个关键点得到。

下一步需要找到每条悬线向左右的最大扩展。对于一条悬线 \((x,y)\sim (x,z)\) 其向左右扩展到的最大值等于所有 \((x,w)(y\leq w\leq z)\) 点向左右尽可能扩展距离的最小值。这可以通过维护每个点的扩展值并每段做前缀 \(\min\) 得到。

例题 P4147 玉蟾宫

基于 \(K\) 复杂度 \(O(K\log^3K)\) 的做法:

对于 这道题\(1\leq K\leq 10^5,1\leq n,m\leq 10^8,10s\) 时限,上述两种做法无法通过。

考虑分治,求所有跨过 \(X=mid\) 这条线的极大子矩形。两边分别建笛卡尔树,树根指向这条线,即左边以 \(y\) 为下标 \(x\) 为值建立大根笛卡尔树,右边建小根树。

设当前关键点数为 \(n\),此时将笛卡尔树上的所有点对应的区间提取出来,令 \(v\) 为其到中间线的距离,分别可以提取到 \(O(n)\) 个区间 \((l,r,v)\)。问题变为从左右各选一个区间 \((l_i,r_i,v_i)(l_j,r_j,v_j)\) 最大化 \((v_i+v_j)\) 与两区间交的乘积。(注意除了每个关键点提取出一个区间,两个关键点之间以平面边界为底也可以提取出区间)

把两区间交分为完全包含和不完全包含两类,对于完全包含是经典二维偏序,容易 \(O(n\log n)\)

不完全包含最大的问题是要保证两区间有交,否则答案是不对的,那么就再进行分治,对于左右分别找到所有 \(l\leq mid < r\) 的区间求它们之间的贡献。容易发现的一点是对于每个区间向下分治时只会至多对一边有贡献,例如条件是 \(l_i\leq l_j\leq mid< r_i\leq r_j\),则 \(i\) 只会在当前和右侧内有贡献,因为对于左侧的 \(j\) 一定有 \(r_j\leq mid < r_i\),这保证了分治的复杂度。

最后我们还需要考虑的是找到了所有 \(l\leq mid < r\) 的区间后如何计算贡献。所以现在相当于有若干 \((r_i,v_i)\)\((l_j,v_j)\) 需要满足上面区间有交的条件,贡献是 \((r_i-l_j)(v_i+v_j)\),我们记这个贡献是 \(w(j,i)\)

将两种区间都按照区间长度从小到大排序,由于笛卡尔树的任意两区间满足要么包含要么不交,所以排序后一定是 \(i\) 包含 \(i-1\),此时对于一个 \((r_i,v_i)\) 他能取的 \(j\) 是一段区间。

同时由于笛卡尔树它们还满足 \(v_i\leq v_{i-1}\),这导致它们有决策单调性。具体的,如果 \(w(j,i)>w(<j,i)\)\(w(j,k)>w(<j,k)\) 对于所有 \((k<i)\),假设所有 \(w(i,j)\)\(i,j\) 都满足合法条件的话。

所以有决策单调性之后这个问题就可以二分加单调栈解决。方法是设 \(i\) 可以被 \(j\in[pl_i,pr_i]\) 转移,找到 \(i\) 的一段区间,使得他们的 \([pl_i,pr_i]\) 有交,取一个在交集内的点 \(x\),从 \(x\) 向左向右做两次决策单调性即可。对于相邻两个决策点 \(a,b\) 可以二分求出一个 \(id\) 使得 \(i\leq id\)\(w(b,i)>w(a,i)\)\(i>id\)\(w(b,i)<w(a,i)\),遍历每个 \(i\),加入合法的所有决策点相邻两个求 \(id\),对 \(id\) 做单调栈,最后即可知道 \(i\) 的最优决策点。

代码
#include<bits/stdc++.h>
#define ll long long
#define fir first
#define sec second
using namespace std;

inline int read(){
	int s=0,k=1;
	char c=getchar();
	while(c>'9'||c<'0'){
		if(c=='-') k=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9'){
		s=(s<<3)+(s<<1)+(c^48);
		c=getchar();
	}
	return s*k;
}

const int N=1e5+5;
int ls[N],rs[N],rt,st[N],top,n,m,K,cs,lsh[N<<2],c[N];
pair<int,int>val[N],sta;
struct SEG{int l,r,v;};
ll ans;

void add(int x,int v){
	while(x<=cs){
		c[x]=max(c[x],v);
		x+=x&-x;
	}	
}

int query(int x){
	int res=0;
	while(x>0){
		res=max(res,c[x]);
		x-=x&-x;
	}
	return res;
}

void Cov(vector<SEG>&Ls,vector<SEG>&Rs){
	for(int i=0;i<=cs;i++) c[i]=0;
	for(int i=0,j=0;i<Ls.size();i++){
		while(j<Rs.size()&&Rs[j].r>=Ls[i].r){
			add(Rs[j].l,Rs[j].v);
			j++;
		}
		ans=max(ans,1ll*(lsh[Ls[i].r]-lsh[Ls[i].l])*(Ls[i].v+1+query(Ls[i].l)));
	}
}

ll w(int x,int y,vector<SEG>&L,vector<SEG>&R){return 1ll*(lsh[R[y].r]-lsh[L[x].l])*(R[y].v+L[x].v+1);}

int get(int x,int y,vector<SEG>&L,vector<SEG>&R){
	int l=0,r=R.size()-1,mid,ans=-1;
	while(l<=r){
		mid=l+r>>1;
		if(w(x,mid,L,R)>=w(y,mid,L,R)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}

void calc(vector<SEG>&L,vector<SEG>&R){
	vector<pair<int,int> >ids;
	for(int i=0,l=0,r=0;i<R.size();i++){
		while(l<L.size()&&L[l].r<R[i].r) l++;
		while(r<L.size()&&L[r].l>=R[i].l) r++;
		ids.emplace_back(l,r-1);
	}
	for(int i=0,j,l,r;i<R.size();i=j){
		if(ids[i].fir>ids[i].sec){
			j=i+1;
			continue;
		}
		j=i;tie(l,r)=ids[i];
		while(j<R.size()){
			if(ids[j].fir>ids[j].sec){
				j++;
				continue;
			}
			if(max(l,ids[j].fir)<=min(r,ids[j].sec)){
				l=max(l,ids[j].fir);
				r=min(r,ids[j].sec);
				j++;
			}
			else break;
		}
		int mid=l+r>>1;
		top=0;
		for(int k=j-1,z=mid+1;k>=i;k--){
			if(ids[k].fir>ids[k].sec) continue;
			while(z>ids[k].fir){
				z--;
				while(top>=2&&get(st[top-1],st[top],L,R)>=get(st[top],z,L,R)) top--;
				st[++top]=z;
				while(top>=2&&get(st[top-1],st[top],L,R)>=k) top--;
			}
			while(top>=2&&get(st[top-1],st[top],L,R)>=k) top--;
			if(top) ans=max(ans,w(st[top],k,L,R));
		}
		top=0;
		for(int k=i,z=mid-1;k<j;k++){
			if(ids[k].fir>ids[k].sec) continue;
			while(z<ids[k].sec){
				z++;
				while(top>=2&&get(st[top],st[top-1],L,R)<=get(z,st[top],L,R)) top--;
				st[++top]=z;
				while(top>=2&&get(st[top],st[top-1],L,R)<k) top--;
			}
			while(top>=2&&get(st[top],st[top-1],L,R)<k) top--;
			if(top) ans=max(ans,w(st[top],k,L,R));
		}	
	}
}

void cdq(int l,int r,vector<SEG>&L,vector<SEG>&R){
	if(L.size()==0||R.size()==0) return ;
	if(l==r) return ;
	int mid=l+r>>1;
	vector<SEG>Lp,Rp,Ls,Rs,Ln,Rn;
	for(SEG x:L){
		if(x.l<=mid&&x.r>mid) Ln.emplace_back(x);
		if(x.l<=mid) Lp.emplace_back(x);
		else Ls.emplace_back(x);
	}
	for(SEG x:R){
		if(x.l<=mid&&x.r>mid) Rn.emplace_back(x);
		if(x.r<=mid) Rp.emplace_back(x);
		else Rs.emplace_back(x);	
	}
	calc(Ln,Rn);
	cdq(l,mid,Lp,Rp);
	cdq(mid+1,r,Ls,Rs);
}

void solve(vector<SEG>&Ls,vector<SEG>&Rs){
	cs=0;
	for(SEG x:Ls) lsh[++cs]=x.l,lsh[++cs]=x.r;
	for(SEG x:Rs) lsh[++cs]=x.l,lsh[++cs]=x.r;
	sort(lsh+1,lsh+1+cs);
	cs=unique(lsh+1,lsh+1+cs)-lsh-1;
	for(SEG &x:Ls) x.l=lower_bound(lsh+1,lsh+1+cs,x.l)-lsh,x.r=lower_bound(lsh+1,lsh+1+cs,x.r)-lsh;
	for(SEG &x:Rs) x.l=lower_bound(lsh+1,lsh+1+cs,x.l)-lsh,x.r=lower_bound(lsh+1,lsh+1+cs,x.r)-lsh;
	sort(Ls.begin(),Ls.end(),[&](SEG x,SEG y){return x.r>y.r;});
	sort(Rs.begin(),Rs.end(),[&](SEG x,SEG y){return x.r>y.r;});
	Cov(Ls,Rs);
	Cov(Rs,Ls);
	sort(Ls.begin(),Ls.end(),[&](SEG x,SEG y){return lsh[x.r]-lsh[x.l]<lsh[y.r]-lsh[y.l];});
	sort(Rs.begin(),Rs.end(),[&](SEG x,SEG y){return lsh[x.r]-lsh[x.l]<lsh[y.r]-lsh[y.l];});
	cdq(1,cs,Ls,Rs);cdq(1,cs,Rs,Ls);
}

void clear(vector<pair<int,int> >&vec){
	int k=1;
	for(int i=1;i<vec.size();i++){
		if(vec[i].sec!=vec[i-1].sec) vec[k++]=vec[i];
		else if(vec[i].sec==vec[i-1].sec&&(i+1>=vec.size()||vec[i].sec!=vec[i+1].sec)) vec[k++]=vec[i];
	}
	while(vec.size()>k) vec.pop_back();
}

void dfsl(int x,int l,int r,int v,vector<SEG>&vec){
	if(x!=-1){
		vec.push_back(SEG({l,r,v-val[x].fir}));
		dfsl(ls[x],l,val[x].sec,v,vec);
		dfsl(rs[x],val[x].sec,r,v,vec);
	}
	else vec.push_back(SEG({l,r,v-sta.fir}));
}

void dfsr(int x,int l,int r,int v,vector<SEG>&vec){
	if(x!=-1){
		vec.push_back(SEG({l,r,val[x].fir-v}));
		dfsr(ls[x],l,val[x].sec,v,vec);
		dfsr(rs[x],val[x].sec,r,v,vec);
	}
	else vec.push_back(SEG({l,r,sta.sec-v}));
}

void solve(int l,int r,vector<pair<int,int> >&vec){
	if(vec.size()==0){
		ans=max(ans,1ll*(r-l)*m);
		return ;
	}
	if(r-l<=1){
		sort(vec.begin(),vec.end(),[&](pair<int,int>a,pair<int,int>b){
			if(a.sec!=b.sec) return a.sec<b.sec;
			else return a.fir<b.fir; 
		});
		clear(vec);
		return ;
	}
	int mid=l+r>>1;
	vector<pair<int,int> >Ls,Rs;
	for(pair<int,int>x:vec){
		if(x.fir<=mid) Ls.emplace_back(x);
		else Rs.emplace_back(x);
	}
	solve(l,mid,Ls);solve(mid+1,r,Rs);
	int i=0,j=0,k=0;
	while(i<Ls.size()&&j<Rs.size()){
		if(Ls[i].sec==Rs[j].sec){
			if(Ls[i].fir<Rs[j].fir) vec[k++]=Ls[i++];
			else vec[k++]=Rs[j++];
		}
		else if(Ls[i].sec<Rs[j].sec) vec[k++]=Ls[i++];
		else vec[k++]=Rs[j++];
	}
	while(i<Ls.size()) vec[k++]=Ls[i++];
	while(j<Rs.size()) vec[k++]=Rs[j++];
	while(vec.size()>k) vec.pop_back();
	clear(vec);
	int lc=0,rc=0;i=0,j=0;
	while(i<Ls.size()){
		if(i+1<Ls.size()){
			if(Ls[i].sec==Ls[i+1].sec) Ls[lc++]=Ls[i+1],i+=2;
			else Ls[lc++]=Ls[i++];
		}
		else Ls[lc++]=Ls[i++];
	}
	while(Ls.size()>lc) Ls.pop_back();
	while(j<Rs.size()){
		if(j+1<Rs.size()){
			if(Rs[j].sec==Rs[j+1].sec) Rs[rc++]=Rs[j],j+=2;
			else Rs[rc++]=Rs[j++];
		}
		else Rs[rc++]=Rs[j++];
	}
	while(Rs.size()>rc) Rs.pop_back();
	sta={l,r};
	vector<SEG>Lc,Rc;
	for(int i=0;i<lc;i++) ls[i]=rs[i]=-1,val[i]=Ls[i];rt=-1;top=0;
	for(int i=0;i<lc;i++){
		while(top&&Ls[st[top]].fir<Ls[i].fir){
			ls[i]=st[top];
			top--;
		}
		if(top) rs[st[top]]=i;
		st[++top]=i;
	}
	if(top) rt=st[1];
	dfsl(rt,0,m,mid,Lc);
	for(int i=0;i<rc;i++) ls[i]=rs[i]=-1,val[i]=Rs[i];rt=-1;top=0;
	for(int i=0;i<rc;i++){
		while(top&&Rs[st[top]].fir>Rs[i].fir){
			ls[i]=st[top];
			top--;
		}
		if(top) rs[st[top]]=i;
		st[++top]=i;
	}
	if(top) rt=st[1];
	dfsr(rt,0,m,mid+1,Rc);
	solve(Lc,Rc);
}

int main(){
	n=read();m=read();K=read();
	vector<pair<int,int> >vec;
	for(int i=1;i<=K;i++){
		int x=read(),y=read();
		vec.emplace_back(x,y);
	}
	ans=m;
	solve(0,n,vec);
	printf("%lld",ans);
	return 0;
}

posted @ 2026-03-25 17:54  programmingysx  阅读(2)  评论(0)    收藏  举报
Title