题解 P2216 【[HAOI2007]理想的正方形】

sshwy

2018-12-08 11:19:05

Solution

# 题意 有一个$a\times b​$的整数组成的矩阵,现请你从中找出一个$n\times n​$的正方形区域,使得该区域所有数中的最大值和最小值的差最小。 # 分析 好多人用什么倍增的算法,还有用线段树的。。。 我们考虑一下,要求以$(i,j)$为右下角的$n\times n$的正方形中的最大值和最小值(以最大值为例) ## 暴力 $n^2$暴力,总复杂度$O(abn^2)$. ## 暴力DP $f[i][j][k]$表示$(i,j)$为右下角,$k\times k$的正方形中的最大值。 $$ f[i][j][k]=max(f[i][j-1][k-1],f[i-1][j][k-1],f[i-1][j-1][k-1]) $$ 复杂度$O(abn)$. ## 倍增DP $f[i][j][k]$表示$(i,j)$为右下角,$2^k\times2^k$的正方形中的最大值 $$ f[i][j][k]=max(f[i][j][k-1],f[i-2^{k-1}][j][k-1],f[i][j-2^{k-1}][k-1],f[i-2^{k-1}][j-2^{k-1}][k-1]) $$ 复杂度$O(ablog_2n)$. ## 单调队列 没错,这就是我要讲的算法 受到暴力DP的启发,我们发现暴力DP用了三个边长小1的正方形来更新当前的正方形 而这有点浪费,因为三个正方形有重复部分(即$(i-k+2,j-k+2)$为左上角,$(i-1,j-1)$为右下角的部分) 那么如何优化呢。。。其实我们可以用互不重叠的一组矩形在凑出正方形啊 考虑$d[i,j]$表示以$(i,j)$为右下角,宽度为$n$,高度为$1$的条状矩形中的最大值 显然对于$d[i,1]\cdots d[i,b]$,可以单调队列一起求啊,也就是维护一个从队首到队尾单调递减队列,保证队首下标与当前下标距离不超过n,然后维护一下 那么有了$d[i][j]$,我们再用$d[i][j]$来一次纵向的宽度为$1$,高度为$n$的条状矩形中的最大值不就完了吗 复杂度$O(ab)$呢 最后,本座为了避免写太多代码,求了$d[i][j]$之后,横竖交换,再求一次横向的即可 # 代码 ```cpp #include<cstdio> #include<cctype> #include<algorithm> #define FOR(i,j,k) for(int i=j;i<=k;i++) #define swap(x,y) (x^=y^=x^=y) using namespace std; const int N=1003; int a,b,n,ans=0x3f3f3f3f; int c[N][N],d[N][N],g[N][N],h[N][N],f[N][N]; //快读 char nc(){ static char buf[100000],*p1=buf,*p2=buf; return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++; } int rd(){ int res=0;char c=nc(); while(!isdigit(c))c=nc(); while(isdigit(c))res=res*10+c-'0',c=nc(); return res; } int calc1(int * v,int * ans){//单调队列计算v[i-n+1..i]的最大值,保存在ans[i]中 int q[N*2][2],l=1,r=0; FOR(i,1,b){ while(l<=r&&q[r][0]<=v[i])r--; while(l<=r&&q[l][1]+n<=i)l++; q[++r][0]=v[i],q[r][1]=i; ans[i]=q[l][0]; } } int calc2(int * v,int * ans){//单调队列计算v[i-n+1..i]的最小值,保存在ans[i]中 int q[N*2][2],l=1,r=0; FOR(i,1,b){ while(l<=r&&q[r][0]>=v[i])r--; while(l<=r&&q[l][1]+n<=i)l++; q[++r][0]=v[i],q[r][1]=i; ans[i]=q[l][0]; } } int main(){ a=rd(),b=rd(),n=rd(); FOR(i,1,a)FOR(j,1,b)c[i][j]=rd();//读入 FOR(i,1,a)calc1(c[i],d[i]),calc2(c[i],h[i]);//横向计算最大最小 FOR(i,1,a)FOR(j,i+1,b)swap(d[i][j],d[j][i]),swap(h[i][j],h[j][i]);//横竖交换 swap(a,b);//横竖交换,之前的横向的最大最小变成了纵向的最大最小 FOR(i,1,a)calc1(d[i],g[i]),calc2(h[i],f[i]);//在之前算出的结果上再次横向计算,得到正方形的最大最小 FOR(i,n,a)FOR(j,n,b)ans=min(ans,g[i][j]-f[i][j]);//统计答案 printf("%d",ans); return 0; } ```