最大子矩形问题
最大子矩形问题
题面:
\(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;
}

浙公网安备 33010602011771号