[bzoj1047][HAOI2007]理想的正方形_动态规划_单调队列
理想的正方形 bzoj-1047 HAOI-2007
题目大意:有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
注释:$2\le a,b,n\le 10^3$,$n\le min(a,b)$。
想法:我的思路简直要死。通常的,我们优化暴力来完成对题目的求解。首先,想暴力。漂亮的暴力没想到,倒是想到一个a*b*n*n的,就是枚举每一个点,如果这个点可以作为n*n正方形的左上角,我就暴力枚举这个正方形的所有点。时间复杂度:O((a-n)*(b-n)*n*n)O(挖坑代填过不去)。想怎么优化:首先我们想,暴力的时候是连续的将n*n个数取最大值,我们可以怎么优化?我们可以对于每一行来讲维护一个窗口长度为n的单调队列来求出以每一个点结尾的前n个数的最值。然后我.. ....tm自以为是正解然后开始敲。自然T了之后还不知道怎么肥四,以为bz评测机又jb炸了。仔细分析了一下发现不太对。总的时间复杂度是a*b*n的,满的话是$10^9$的,不T才怪... ...想想再优化优化就好了嘛。对于求出来的矩阵,maxn[i][j]表示原矩阵以(i,j)为起点前n个数的最值。我们可以对maxn矩阵再维护一个窗口长度为n的单调队列,这样的话查询就是O(1)的了qwq.
最后,附上丑陋的代码... ...
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define inf 2000000000 #define ll long long using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int a,b,n; int v[1005][1005],mx[1005][1005],mn[1005][1005],t1[1005],t2[1005]; int val[1005],pos[1005]; void pre() { int l,r; for(int i=1;i<=a;i++) { l=1,r=1; for(int j=1;j<=b;j++) { while(l<r&&val[r-1]<=v[i][j])r--; val[r]=v[i][j];pos[r]=j;r++; if(pos[l]==j-n)l++; if(j>=n)mx[i][j]=val[l]; } l=1,r=1; for(int j=1;j<=b;j++) { while(l<r&&val[r-1]>=v[i][j])r--; val[r]=v[i][j];pos[r]=j;r++; if(pos[l]==j-n)l++; if(j>=n)mn[i][j]=val[l]; } } } void solve() { int ans=inf; int l,r; for(int i=n;i<=b;i++) { l=1,r=1; for(int j=1;j<=a;j++) { while(l<r&&val[r-1]>=mn[j][i])r--; val[r]=mn[j][i];pos[r]=j;r++; if(pos[l]==j-n)l++; if(j>=n)t1[j]=val[l]; } l=1,r=1; for(int j=1;j<=a;j++) { while(l<r&&val[r-1]<=mx[j][i])r--; val[r]=mx[j][i];pos[r]=j;r++; if(pos[l]==j-n)l++; if(j>=n)t2[j]=val[l]; } for(int i=n;i<=a;i++)ans=min(ans,t2[i]-t1[i]); } printf("%d\n",ans); } int main() { a=read();b=read();n=read(); for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) v[i][j]=read(); pre(); solve(); return 0; }
小结:单调队列是优化dp的一种比较优秀的手段。而且好写好调,比什么单调栈强多了... ...
| 欢迎来原网站坐坐! >原文链接<