有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。
(1)矩阵中的所有数都不超过1,000,000,000
(2)20%的数据2<=a,b<=100,n<=a,n<=b,n<=10
(3)100%的数据2<=a,b<=1000,n<=a,n<=b,n<=100
首先想出的是st表形式,然后用了3维表达,加上了快读卡常:
#include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; int a,b,n; const int N=1003,M=103; int mn[N][N][M],mx[N][N][M]; inline int read() { int x=0;char c=getchar(); while(c<'0' || c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return x; } int main() { a=read(),b=read(),n=read(); for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) mx[i][j][1]=mn[i][j][1]=read(); for(int i=2;i<=n;i++) for(int sx=a-i+1;sx;sx--) for(int sy=b-i+1;sy;sy--) { mn[sx][sy][i]=min( min(mn[sx][sy][i-1],mn[sx+1][sy+1][i-1]) ,min(mn[sx][sy+1][i-1],mn[sx+1][sy][i-1]) ); mx[sx][sy][i]=max( max(mx[sx][sy][i-1],mx[sx+1][sy+1][i-1]) ,max(mx[sx][sy+1][i-1],mx[sx+1][sy][i-1]) ); } int ans=1<<30; for(int sx=a-n+1;sx;sx--) for(int sy=b-n+1;sy;sy--) ans=min(ans,mx[sx][sy][n]-mn[sx][sy][n]); printf("%d ",ans); return 0; }
然后结果:20->20
卡常无效,估计是原始代码太慢了
然后按着题解减了一维......
20->60,神奇!
所以减去一维有可能大程度加快速度
#include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; int a,b,n; const int N=1003,M=103; int mn[N][N],mx[N][N]; inline int read() { int x=0;char c=getchar(); while(c<'0' || c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return x; } int main() { a=read(),b=read(),n=read(); for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) mx[i][j]=mn[i][j]=read(); for(int i=2;i<=n;i++) { int r1=a-i+1; for(int sx=1;sx<=r1;sx++) { int r2=b-i+1; for(int sy=1;sy<=r2;sy++) { mn[sx][sy]=min( min(mn[sx][sy],mn[sx+1][sy+1]) ,min(mn[sx][sy+1],mn[sx+1][sy]) ); mx[sx][sy]=max( max(mx[sx][sy],mx[sx+1][sy+1]) ,max(mx[sx][sy+1],mx[sx+1][sy]) ); } } } int ans=1<<30; for(int sx=a-n+1;sx;sx--) for(int sy=b-n+1;sy;sy--) ans=min(ans,mx[sx][sy]-mn[sx][sy]); printf("%d ",ans); return 0; }
然后一个快读,60->70
最后一个O2,过了,
然后去测别的评测机速度,不开O2全过......
所以减维技巧要学好
好了接下来是正经优化,
(1)RMQ
将O(n)的维护时间变成O(logn)
注意:log2和log都是关键词,不能做变量名
#include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> using namespace std; int a,b,n; const int N=1003,M=103; int lg[N],d[8]; int mn[N][N][8],mx[N][N][8]; inline int read() { int x=0;char c=getchar(); while(c<'0' || c>'9') c=getchar(); while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return x; } int main() { a=read(),b=read(),n=read(); for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1; d[0]=1; for(int i=1;i<=lg[n];i++) d[i]=d[i-1]<<1; for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) mx[i][j][0]=mn[i][j][0]=read(); int ans=1<<30,t1=lg[n],t2=d[t1]; for(int i=1;i<=t1;i++) { int r1=a-d[i]+1; for(int sx=1;sx<=r1;sx++) { int r2=b-d[i]+1; for(int sy=1;sy<=r2;sy++) { mn[sx][sy][i]=min( min(mn[sx][sy][i-1],mn[sx+d[i-1]][sy+d[i-1]][i-1]) ,min(mn[sx][sy+d[i-1]][i-1],mn[sx+d[i-1]][sy][i-1]) ); mx[sx][sy][i]=max( max(mx[sx][sy][i-1],mx[sx+d[i-1]][sy+d[i-1]][i-1]) ,max(mx[sx][sy+d[i-1]][i-1],mx[sx+d[i-1]][sy][i-1]) ); } } } for(int sx=a-n+1,ex=a+1;sx>0;sx--,ex--) for(int sy=b-n+1,ey=b+1;sy>0;sy--,ey--) { int a1=max( max(mx[sx][sy][t1],mx[sx][ey-t2][t1] ),max(mx[ex-t2][sy][t1],mx[ex-t2][ey-t2][t1]) ); int a2=min( min(mn[sx][sy][t1],mn[sx][ey-t2][t1] ),min(mn[ex-t2][sy][t1],mn[ex-t2][ey-t2][t1]) ); ans=min(ans,a1-a2); } printf("%d ",ans); return 0; }
(2)deque
当做二位的线性单调dp来做
复制自luogu:
我的思路是...用单调队列分别维护行与列。
具体实现方法:是先用单调队列对每一行的值维护,并将a[][]每个区间的最大值,最小值分别存在X[][]和x[][]中。
那么X[][]与x[][]所存储的分别是1×n的长方形内的最大值,最小值。X[i][j]存储第i行第j~j+n-1列的长方形中的最大值。同理,x[i][j]存储第i行第j~j+n-1列的长方形中的最小值。
这时再对这两个数组的每一列上的值进行维护,将X[][]中每个区间的的最大值用Y[][]维护,将x[][]中的每个区间的最小值用y[][]维护。那么Y[i][j]存储X[][]中第i~i+n-1行第j列的长方形的最大值。同理y[i][j]存储x[][]中第i~i+n-1行第j列的长方形的最小值。
故Y[i][j]存储的实为以a[i~i+n-1][j~j+n-1]中的最大,即以i,j为左上角,边长为n的正方形中的最大值。同理,y[i][j]存储的即以i,j为左上角,边长为n的正方形中的最小值。
#include<cstdio> #include<cstdlib> #include<queue> #include<algorithm> using namespace std; int a,b,n; const int N=1003,M=103; int d[N][N]; int mx[2][N][N],mn[2][N][N];//0: (i,j)表示的是max(1,j-n+1)到j的 的最值,所以有效部分从n开始 deque <int > q1,q2; int main() { scanf("%d%d%d",&a,&b,&n); for(int i=1;i<=a;i++) { q1.clear() ,q2.clear() ; for(int j=1;j<=b;j++) { scanf("%d",&d[i][j]); if(!q1.empty() && q1.front() <= j-n) q1.pop_front() ; while(!q1.empty() && d[i][q1.back() ]<=d[i][j] ) q1.pop_back() ; q1.push_back(j); mx[0][i][j]=d[i][q1.front() ]; if(!q2.empty() && q2.front() <= j-n) q2.pop_front() ; while(!q2.empty() && d[i][q2.back() ]>=d[i][j] ) q2.pop_back() ; q2.push_back(j); mn[0][i][j]=d[i][q2.front() ]; } } for(int j=1;j<=b;j++) { q1.clear() ,q2.clear() ; for(int i=1;i<=a;i++) { if(!q1.empty() && q1.front() <= i-n) q1.pop_front() ; while(!q1.empty() && mx[0][q1.back() ][j]<=mx[0][i][j] ) q1.pop_back() ; q1.push_back(i); mx[1][i][j]=mx[0][q1.front() ][j]; if(!q2.empty() && q2.front() <= i-n) q2.pop_front() ; while(!q2.empty() && mn[0][q2.back() ][j]>=mn[0][i][j] ) q2.pop_back() ; q2.push_back(i); mn[1][i][j]=mn[0][q2.front() ][j]; } } int ans=mx[1][n][n]-mn[1][n][n]; for(int i=n;i<=a;i++) for(int j=n;j<=b;j++) ans=min(ans,mx[1][i][j]-mn[1][i][j]); printf("%d ",ans); return 0; }