[HAOI2007]理想的正方形
传送
UPD.2021.03.09
这道题有两种做法,一种是单调队列,另一种是二维st表。
单调队列的做法就是枚举上边界\(i\),这样就能确定矩形的下边界为\(i+r-1\)。在这个范围内,用每一个竖条的最大值(最小值)代替整个竖条,就变成了滑动窗口问题了。
代码里存了原矩阵和元素为相反数的矩阵,这样只用调用同一个函数两遍就能求最大值和最小值了,看起来简单些。
时间复杂度\(O(n^2r)\),虽然可以预处理出\(n\)个st表在\(O(1)\)时间内求竖条最值,使复杂度达到\(O(n^2logn+n^2)\),但没别要.
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<queue>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const int maxn = 1e3 + 5;
In ll read()
{
ll ans = 0;
char ch = getchar(), las = ' ';
while(!isdigit(ch)) las = ch, ch = getchar();
while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
if(las == '-') ans = -ans;
return ans;
}
In void write(ll x)
{
if(x < 0) x = -x, putchar('-');
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
int n, m, r;
#define pr pair<int, int>
#define mp make_pair
#define F first
#define S second
struct Node
{
int a[maxn][maxn];
deque<pr> q;
In void calc(int x, int y)
{
int Max = -INF;
for(int i = 0; i < r; ++i) Max = max(Max, a[x + i][y]);
while(!q.empty() && q.back().F < Max) q.pop_back();
while(!q.empty() && q.front().S <= y - r) q.pop_front();
q.push_back(mp(Max, y));
}
}Ma, Mi;
In int solve(int x)
{
Ma.q.clear(), Mi.q.clear();
int ret = INF;
for(int i = 1; i <= m; ++i)
{
Ma.calc(x, i), Mi.calc(x, i);
if(i >= r) ret = min(ret, Ma.q.front().F + Mi.q.front().F);
}
return ret;
}
int main()
{
n = read(), m = read(), r = read();
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) Ma.a[i][j] = read(), Mi.a[i][j] = -Ma.a[i][j];
int ans = INF;
for(int i = 1; i <= n - r + 1; ++i) ans = min(ans, solve(i));
write(ans), enter;
return 0;
}
另一种做法是二维st表。 预处理的时候,一个大矩形的最值由四个小矩形的最值合并而来。 查询的时候,在$n*n$的矩阵中找到四个最大且边长不找过$n$的$2^k$的矩形。
然后就可以了,时间复杂度\(O(n^2logn+n^2)\).
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<stack>
#include<queue>
#include<vector>
#include<cctype>
using namespace std;
#define enter puts("")
#define space putchar(' ')
#define Mem(a) memset(a, 0, sizeof(a))
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 1e3 + 5;
inline ll read()
{
ll ans = 0;
char ch = getchar(), last = ' ';
while(!isdigit(ch)) {last = ch; ch = getchar();}
while(isdigit(ch)) {ans = ans * 10 + ch - '0'; ch = getchar();}
if(last == '-') ans = -ans;
return ans;
}
inline void write(ll x)
{
if(x < 0) putchar('-'), x = -x;
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
int x, y, n, a[maxn][maxn];
int Max[maxn][maxn][7], Min[maxn][maxn][7], nk;
void rmq()
{
for(int i = 1; i <= x; ++i)
for(int j = 1; j <= y; ++j) Max[i][j][0] = Min[i][j][0] = a[i][j];
for(int k = 1; (1 << k) <= n; ++k)
for(int i = 1; i + (1 << k) - 1 <= x; ++i)
for(int j = 1; j + (1 << k) - 1 <= y; ++j)
{
Max[i][j][k] = max(max(Max[i][j][k - 1], Max[i][j + (1 << (k - 1))][k - 1]), max(Max[i + (1 << (k - 1))][j][k - 1], Max[i + (1 << (k - 1))][j + (1 << (k - 1))][k - 1]));
Min[i][j][k] = min(min(Min[i][j][k - 1], Min[i][j + (1 << (k - 1))][k - 1]), min(Min[i + (1 << (k - 1))][j][k - 1], Min[i + (1 << (k - 1))][j + (1 << (k - 1))][k - 1]));
}
while((1 << (nk + 1)) <= n) nk++;
}
int ans = 2147483647;
int main()
{
x = read(); y = read(); n = read();
for(int i = 1; i <= x; ++i)
for(int j = 1; j <= y; ++j) a[i][j] = read();
rmq();
for(int i = 1; i <= x - n + 1; ++i)
for(int j = 1; j <= y - n + 1; ++j)
{
int _max = max(max(Max[i][j][nk], Max[i][j + n - (1 << nk)][nk]), max(Max[i + n - (1 << nk)][j][nk], Max[i + n - (1 << nk)][j + n - (1 << nk)][nk]));
int _min = min(min(Min[i][j][nk], Min[i][j + n - (1 << nk)][nk]), min(Min[i + n - (1 << nk)][j][nk], Min[i + n - (1 << nk)][j + n - (1 << nk)][nk]));
ans = min(ans, _max - _min);
}
write(ans); enter;
return 0;
}