P2216 [HAOI2007]理想的正方形# 一句话总结:二维滑动窗口

看到题目,首先打开标签,看到一个$RMQ$

第一反应:二维树状数组!

然鹅我并不会二维维护最大最小值。。

第二反应:二维线段树!!

然鹅会超时(实测)(其实是写的太丑了

第三反应:二维st表!

可行!

但是转眼一看,标签里面还有个单调队列

emmm..(回忆起了一道题

于是就有了我们的一句话总结:二维滑动窗口

//单调队列板子
register int head=1,tail=0;
for(int i=1;i<=n;i++){//最大
    while(head<=tail&&q[tail]>=a[i]) --tail;
    q[++tail]=a[i];
    pos[tail]=i;
    if(pos[head]<=i-m) ++head;
    if(i>=m) write(q[head]),putchar(' ');
}
putchar('\n');

head=1,tail=0;
for(int i=1;i<=n;i++){//最小
    while(head<=tail&&q[tail]<=a[i]) --tail;
    q[++tail]=a[i];
    pos[tail]=i;
    if(pos[head]<=i-m) ++head;
    if(i>=m) write(q[head]),putchar(' ');
}
putchar('\n');

然鹅问题又出来了,等我去翻滑动窗口的记录时,我发现自己是用线段树氵过的。。。(于是就重新学了一遍

分析:

滑动窗口是在一条直线上,只用一维维护;

而本题需要找二维的正方形,听起来似乎不难,而且实际上也是如此。

我们需要找到一个办法可以把每个$n×n$的正方形中的最大值和最小值维护起来;

而第一反应当然就是对行维护一个最大一个最小,再对列维护一个最大一个最小

这样似乎可行,但是怎么统计答案呢?

因为每个行和列数组的最大最小值不一定代表这个正方形的最大和最小

所以在这停顿,换个想法:对行数组再进行一次单调对列!

这个方法明显可行!因为对行数组在纵方向上再进行一次统计,覆盖的范围就变成二维的了

最后统计答案最大数组$n$到$a$行,$n$到$b$列减去答案最小数组对应位置的最小值即可

具体代码

#include<bits/stdc++.h>
#define N 3005
#define inf 0x3f3f3f3f
#define endl '\a'
using namespace std;
int a,b,n,ans=inf;//行数,列数,正方形边长、答案
int mp[N][N];//原数组
int maxn[N][N],minn[N][N];//对每一行处理后的最大最小(以及省空间也拿来当作答案数组了)
int q[N],pos[N];//单调队列的元素和位置
inline int read(){
	register int f=1,n=0;
	register char c=getchar();
	while(c!='-'&&(c<'0'||c>'9')) c=getchar();
	if(c=='-') f=-1,c=getchar();
	while(c>='0'&&c<='9') n=(n<<3)+(n<<1)+(c^48),c=getchar();
	return f*n;
}
inline void write(register int x){
    if(x<0) x=-x, putchar('-');
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
inline void row_max(int row){//对每一行进行单调队列找最大
    register int head=1,tail=0;
    for(int i=1;i<=b;i++){
        while(head<=tail&&q[tail]<=mp[row][i]) --tail;
        q[++tail]=mp[row][i];
        pos[tail]=i;
        if(pos[head]<=i-n) ++head;
        if(i>=n) maxn[row][i]=q[head];//将最大值存在对应位置的行数组中
    }
}
inline void row_min(int row){//对每一行进行单调队列找最小
    register int head=1,tail=0;
    for(int i=1;i<=b;i++){
        while(head<=tail&&q[tail]>=mp[row][i]) --tail;
        q[++tail]=mp[row][i];
        pos[tail]=i;
        if(pos[head]<=i-n) ++head;
        if(i>=n) minn[row][i]=q[head];//将最小值存在对应位置的行数组中
    }
}
inline void matrix_max(int column){//对行数组在纵方向上再找一次最大
    register int head=1,tail=0;
    for(int i=1;i<=a;i++){
        while(head<=tail&&q[tail]<=maxn[i][column]) --tail;
        q[++tail]=maxn[i][column];
        pos[tail]=i;
        if(pos[head]<=i-n) ++head;
        if(i>=n) maxn[i][column]=q[head];//省空间,把这个数组又拿来用
        //因为单调队列中每个元素只会被遍历一次,所以可以放心用完就扔
    }
}
inline void matrix_min(int column){//对行数组在纵方向上再找一次最小
    register int head=1,tail=0;
    for(int i=1;i<=a;i++){
        while(head<=tail&&q[tail]>=minn[i][column]) --tail;
        q[++tail]=minn[i][column];
        pos[tail]=i;
        if(pos[head]<=i-n) ++head;
        if(i>=n) minn[i][column]=q[head];
    }
}
inline void debug(){//调试
    for(int i=1;i<=a;i++){for(int j=1;j<=b;j++)cout<<maxn[i][j]<<' ';cout<<endl;}cout<<endl<<endl;
    for(int i=1;i<=a;i++){for(int j=1;j<=b;j++)cout<<minn[i][j]<<' ';cout<<endl;}cout<<endl<<endl;
}
main(void){
    freopen("1108.in","r",stdin);
    a=read();b=read();n=read();//行数,列数,正方形边长
    for(int i=1;i<=a;i++)
    for(int j=1;j<=b;j++) mp[i][j]=read();//读入矩阵
    for(int i=1;i<=a;i++) row_max(i),row_min(i);//对原矩阵的每一行都找一次最大和最小
    for(int i=n;i<=b;i++) matrix_max(i),matrix_min(i);//对行数组的每一列都找一次最大和最小
    // debug();
    for(int i=n;i<=a;i++)
    for(int j=n;j<=b;j++) ans=min(ans,maxn[i][j]-minn[i][j]);//统计答案
    write(ans),putchar('\a');
    return 0;
}

码风较奇葩(压行强迫症+卡常小能手

欢迎觉得做法很麻烦或有错误的巨佬来喷