标签:简单 需要 std 两种 haoi2007 math break cst 入队
这道题用到单调队列以及降维处理。
之前我并没有学过单调队列,第一眼看到直接想法是二维线段树,但是我并不会写二维线段树,而且时间复杂度也很玄乎。
于是补了一波单调队列。
简单说一点。好久了我好像快忘了。
单调队列的作用是求一个滑动区间的最值。
比如对于一个长度为1000的数列,求以每个数开始长度为7的区间的最小值。就好像这个区间在均匀滑动。
单调队列的思想是没用的直接丢掉。
假设我要求区间长度为 m 的区间最小值,现在当前区间有 L 个数已经在队列 Q 中且满足单调递增。
现在考虑要新加入的这个元素 x 。
有两种情况。
1:x > Q [ L ] 。直接将 x 入队。
2:x <= Q [ L ] 。这时候因为我们要求的是最小值且区间在滑动,而元素 Q [ L ] 先于 x 入队,将来也一定先于 x 出队 。也就是说元素 Q [ L ] 在队列中的时候,元素 x 一定在队列中,且因为 x <= Q [ L ],Q [ L ] 对区间最值已经不会有贡献了,多以我们将 Q [ L ] 出队,且对于队列中的所有比 x 大或等的元素都出队,一样的道理。最后将 x 入队。
每次输出队首元素即可。
对于求区间最大值我们保证队列元素递减。
注意区间在滑动,所以我们要记录队列中的元素在原数列中的位置,如果已经不在区间范围内就将其出队。区间每次滑动一,每次最多因为这种情况出队一个元素,且该元素一定为队首元素。
数列中每个数最多入队1次,出队1次。
时间复杂度O ( n ) 。
有了单调队列,来解决这道问题。
1:我们对每行求出长度为 n 的滑动区间最值记录在 maxx[i][j] ,minx[i][j] 中。
此时 maxx[i][j] 存储的是第 i 行区间 [ j , j + n - 1 ] 的最大值,minx[i][j]为最小值。
2:我们对每一列 ( 对 minx[][] 和 maxx[][] ) 求出长度为 n 的滑动区间最值。
在求的时候直接比较 max - min 的最大值并记录,最后输出就可以了。
这就是降维思想,第二次跑单调队列时出来的最值已经是 n * n 区间的最值了。
我表达能力不好。
单调队列在写的时候细节是需要注意的。
// q.c #include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> using namespace std; const int M=1000+10; int a,b,n,w[M][M],wMin[M][M],wMax[M][M],h1,t1,h2,t2,qMin[M],qMax[M],pos1[M],pos2[M],ans=2*(int)1e9+7; void reset() { h1=h2=1; t1=t2=0; memset(qMin,0,sizeof(qMin)); memset(qMax,0,sizeof(qMax)); memset(pos1,0,sizeof(pos1)); memset(pos2,0,sizeof(pos2)); } void insert1(int x,int y,int z,int r) { while(t1>=h1&&qMin[t1]>=r) t1--; qMin[++t1]=r,pos1[t1]=z; } void insert2(int x,int y,int z,int r) { while(t2>=h2&&qMax[t2]<=r) t2--; qMax[++t2]=r,pos2[t2]=z; } int main() { freopen("square.in","r",stdin); freopen("square.out","w",stdout); scanf("%d%d%d",&a,&b,&n); for(int i=1;i<=a;i++) for(int j=1;j<=b;j++) scanf("%d",&w[i][j]); for(int i=1;i<=a;i++) { reset(); for(int j=1;j<=n;j++) { insert1(i,j,j,w[i][j]); insert2(i,j,j,w[i][j]); } for(int j=n+1;j<=b+1;j++) { wMin[i][j-n]=qMin[h1]; wMax[i][j-n]=qMax[h2]; if(j==b+1) break; insert1(i,j,j,w[i][j]); insert2(i,j,j,w[i][j]); if(pos1[h1]<=j-n) h1++; if(pos2[h2]<=j-n) h2++; } } for(int j=1;j<=b-n+1;j++) { reset(); for(int i=1;i<=n;i++) { insert1(i,j,i,wMin[i][j]); insert2(i,j,i,wMax[i][j]); } for(int i=n+1;i<=a+1;i++) { ans=min(ans,qMax[h2]-qMin[h1]); if(i==a+1) break; insert1(i,j,i,wMin[i][j]); insert2(i,j,i,wMax[i][j]); if(pos1[h1]<=i-n) h1++; if(pos2[h2]<=i-n) h2++; } } printf("%d\n",ans); return 0; }
标签:简单 需要 std 两种 haoi2007 math break cst 入队
原文地址:https://www.cnblogs.com/qjs12/p/8794766.html