(单调队列、ST 表)洛谷 P2216 HAOI2007 理想的正方形 / P2219 HAOI2007 修筑绿化带
怎么能同一年省选出一种类型的题目呢……
1.理想的正方形
题意
有一个 a×ba \times ba×b 的整数组成的矩阵,现请你从中找出一个 n×nn \times nn×n 的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
2≤a,b≤10002 \le a,b \le 10002≤a,b≤1000,n≤min(a,b,100)n\le \min(a,b,100)n≤min(a,b,100),所有整数不超过 10910^9109。
思路
以前做子矩形问题的时候我们会限定行的上下界 [l,r][l,r][l,r],然后一起扫,时间复杂度 O(n3)O(n^3)O(n3),但是这题并不支持这个时间复杂度。
不过这题固定了正方形的大小。我们想要查询,对于 jjj 列,i>ni>ni>n 行向上 nnn 格,长度为 nnn 的区间(ai−n+1∼i,ja_{i-n+1\sim i,j}ai−n+1∼i,j)内的最大 / 小值 ma/mij,ima/mi_{j,i}ma/mij,i。然后对于 >n>n>n 的行扫过去维护长度为 nnn 的 ma,mima,mima,mi 最小值。
这个可以单调队列维护——弹出不合法队头,弹出不优于当前决策点的队尾,然后加入当前决策点。维护 ma,mima,mima,mi 可以分别为胡单调递减 / 单调递增,就能最优答案取队头。
于是用 444 个单调队列来做即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2004,inf=3e14;
ll h,w,n;
ll a[N][N];
ll ma[N][N],mi[N][N];
ll q1[N],q2[N];
int main()
{scanf("%lld%lld%lld",&h,&w,&n);for(int i=1;i<=h;i++)for(int j=1;j<=w;j++)scanf("%lld",&a[i][j]);for(int j=1;j<=w;j++){memset(q1,0,sizeof(q1));ll hh=1,tt=0;for(int i=1;i<=h;i++){while(hh<=tt&&i-q1[hh]+1>n)hh++;while(hh<=tt&&a[q1[tt]][j]<a[i][j])tt--;q1[++tt]=i;if(i>=n)ma[j][i]=a[q1[hh]][j];}}for(int j=1;j<=w;j++){memset(q2,0,sizeof(q2));ll hh=1,tt=0;for(int i=1;i<=h;i++){while(hh<=tt&&i-q2[hh]+1>n)hh++;while(hh<=tt&&a[q2[tt]][j]>a[i][j])tt--;q2[++tt]=i;if(i>=n)mi[j][i]=a[q2[hh]][j];}}ll ret=inf;for(int l=1;l+n-1<=h;l++){ll r=l+n-1;memset(q1,0,sizeof(q1));memset(q2,0,sizeof(q2));ll h1=1,t1=0,h2=1,t2=0;for(int i=1;i<=w;i++){while(h1<=t1&&i-q1[h1]+1>n)h1++;while(h2<=t2&&i-q2[h2]+1>n)h2++;while(h1<=t1&&ma[q1[t1]][r]<ma[i][r])t1--;while(h2<=t2&&mi[q2[t2]][r]>mi[i][r])t2--;q1[++t1]=i;q2[++t2]=i;if(i>=n)ret=min(ret,ma[q1[h1]][r]-mi[q2[h2]][r]);}}printf("%lld",ret);return 0;
}
2.修筑绿化带
题意
为了增添公园的景致,现在需要在公园中修筑一个花坛,同时在花坛四周修建一片绿化带,让花坛被绿化带围起来。
如果把公园看成一个 M×NM\times NM×N 的矩形,那么花坛可以看成一个 C×DC\times DC×D 的矩形,绿化带和花坛一起可以看成一个 A×BA\times BA×B 的矩形。
如果将花园中的每一块土地的“肥沃度”定义为该块土地上每一个小块肥沃度之和,那么,绿化带的肥沃度为 A×BA\times BA×B 块的肥沃度减去 C×DC\times DC×D 块的肥沃度。
为了使得绿化带的生长得旺盛,我们希望绿化带的肥沃度最大。
1≤M,N≤10001\leq M,N\leq 10001≤M,N≤1000,1≤A≤M1\leq A\leq M1≤A≤M,1≤B≤N1\leq B\leq N1≤B≤N,1≤C≤A−21\leq C\leq A-21≤C≤A−2,1≤D≤B−21\leq D\leq B-21≤D≤B−2,1≤xi,j≤1001\leq x_{i,j}\leq 1001≤xi,j≤100。
思路
这个题要转化一下。记 S(x1,y1,x2,y2)S(x_1,y_1,x_2,y_2)S(x1,y1,x2,y2) 表示矩形 (x1,y1)−(x2,y2)(x_1,y_1)-(x_2,y_2)(x1,y1)−(x2,y2) 的肥沃度之和,即:
S(x1−A+1,y1−B+1,x1,y1)−S(x2−C+1,y2−D+1,x2,y2)S(x_1-A+1,y_1-B+1,x_1,y_1)-S(x_2-C+1,y_2-D+1,x_2,y_2)S(x1−A+1,y1−B+1,x1,y1)−S(x2−C+1,y2−D+1,x2,y2)
其中:
x1−A+1+1≤x2−C+1,x2≤x1−1y1−B+1+1≤y2−D+1,y2≤y1−1\begin{matrix}
x_1-A+1+1\le x_2-C+1,x_2\le x_1-1\\ 
y_1-B+1+1\le y_2-D+1, y_2\le y_1-1
\end{matrix}x1−A+1+1≤x2−C+1,x2≤x1−1y1−B+1+1≤y2−D+1,y2≤y1−1
这个限制是要给草地留边框。可以把限制条件化简成两个区间:
x1−(A−C−1)≤x2≤x1−1y1−(B−D−1)≤y2≤y1−1\begin{matrix}
x_1-(A-C-1)\le x_2\le x_1-1\\ 
y_1-(B-D-1)\le y_2\le y_1-1
\end{matrix}x1−(A−C−1)≤x2≤x1−1y1−(B−D−1)≤y2≤y1−1
预处理 huai,j,caoi,jhua_{i,j},cao_{i,j}huai,j,caoi,j 表示以 (i,j)(i,j)(i,j) 为右下角的花朵 / 草坪,相当于限定了 caoi,jcao_{i,j}caoi,j,要在矩形 (i−(A−C−1),j−(B−D−1))−(i−1,j−1)(i-(A-C-1),j-(B-D-1))-(i-1,j-1)(i−(A−C−1),j−(B−D−1))−(i−1,j−1) 矩形上找一个最小的 huai,jhua_{i,j}huai,j ——这已经变成 1.1.1. 了。
不过因为矩形长度那里我调了很久,于是找每列的最小值就用了 ST 表了。其实和 1.1.1. 思路一样。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=1002,M=12;
ll n,m,a,b,c,d;
ll g[N][N];
ll s[N][N];
ll hua[N][N],cao[N][N];
ll sum(ll xa,ll ya,ll xb,ll yb)
{return s[xb][yb]-s[xa-1][yb]-s[xb][ya-1]+s[xa-1][ya-1];
}
ll q2[N],fmi[N][N][M];
ll lg2[N];
void init()
{lg2[1]=0;lg2[2]=1;for(int i=3;i<N;i++)lg2[i]=lg2[i/2]+1;
}
ll query_mi(ll j,ll l,ll r)
{ll s=lg2[r-l+1];return min(fmi[j][l][s],fmi[j][r-(1<<s)+1][s]);
}
int main()
{init();scanf("%lld%lld",&n,&m);scanf("%lld%lld%lld%lld",&a,&b,&c,&d);for(int i=1;i<=n;i++){for(int j=1;j<=m;j++){scanf("%lld",&g[i][j]);}}for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+g[i][j];for(int i=a;i<=n;i++)for(int j=b;j<=m;j++)cao[i][j]=sum(i-a+1,j-b+1,i,j);for(int i=c;i<=n;i++)for(int j=d;j<=m;j++)hua[i][j]=sum(i-c+1,j-d+1,i,j);for(int j=d;j<=m;j++){for(int i=1;i<=n;i++)fmi[j][i][0]=hua[i][j];for(int t=1;t<=lg2[n];t++)for(int i=1;i+(1<<t)-1<=n;i++)fmi[j][i][t]=min(fmi[j][i][t-1],fmi[j][i+(1<<(t-1))][t-1]);}ll ret=0;for(int i=a;i<=n;i++){memset(q2,0,sizeof(q2));ll h2=1,t2=0;for(int j=2;j<=m;j++){while(h2<=t2&&j-1-q2[h2]+1>b-d-1)h2++;while(h2<=t2&&query_mi(q2[t2],i-1-(a-c-1)+1,i-1)>query_mi(j-1,i-1-(a-c-1)+1,i-1))t2--;q2[++t2]=j-1;if(j>=b)ret=max(ret,cao[i][j]-query_mi(q2[h2],i-1-(a-c-1)+1,i-1));}}printf("%lld",ret);return 0;
}
