1. 题目
传送门= ̄ω ̄=
题意:
在一个 A×B的矩阵中找出一个 N×N的矩阵使得该矩阵内最大值与最小值的差值最小。
数据范围 A,B≤103,n≤102
2. 题解
听说正解是动态规划 QvQ,但我的是暴力堆+哈希表。。。
害怕.jpg
但是我沉迷暴力的数据结构,于是先写了个 kd-tree 版的,然后毫无疑问地 TLE 了
代码如下(会 T 不用试了)
#include <bits/stdc++.h>
#define DS (2)
#define NS (1005)
#define INF (1000000005)
#define FIR first
#define SEC second
using namespace std;
typedef pair<int, int> PII;
template <typename _Tp> inline void IN(_Tp& dig)
{
char c; bool flag = 0; dig = 0;
while (c = getchar(), !isdigit(c));
while (isdigit(c)) dig = dig * 10 + c - '0', c = getchar();
}
int A, B, n, root, sz, D, ans = INT_MAX;
struct N
{
int d[DS], mnd, mxd, data, mn[DS], mx[DS], l, r;
int& operator [] (const int a){return d[a];}
}arr[NS * NS], e[NS * NS];
bool cmp(N a, N b){return a[D] < b[D];}
void pup(int a)
{
int l = e[a].l, r = e[a].r;
if (l)
{
e[a].mnd = min(e[a].mnd, e[l].mnd);
e[a].mxd = max(e[a].mxd, e[l].mxd);
}
if (r)
{
e[a].mnd = min(e[a].mnd, e[r].mnd);
e[a].mxd = max(e[a].mxd, e[r].mxd);
}
for (int i = 0; i < DS; i += 1)
{
e[a].mn[i] = e[a].mx[i] = e[a][i];
if (l)
{
e[a].mn[i] = min(e[a].mn[i], e[l].mn[i]);
e[a].mx[i] = max(e[a].mx[i], e[l].mx[i]);
}
if (r)
{
e[a].mn[i] = min(e[a].mn[i], e[r].mn[i]);
e[a].mx[i] = max(e[a].mx[i], e[r].mx[i]);
}
}
}
int Build(int l, int r, int d = 0)
{
if (l > r) return 0;
D = d;
int mid = ((l + r) >> 1), a = ++sz;
nth_element(arr + l, arr + mid, arr + r + 1, cmp);
e[a] = arr[mid];
e[a].l = Build(l, mid - 1, d ^ 1);
e[a].r = Build(mid + 1, r, d ^ 1);
pup(a); return a;
}
bool jud_in(int x1, int y1, int x2, int y2, int a)
{
return x1 <= e[a].mn[0] && y1 <= e[a].mn[1] \
&& x2 >= e[a].mx[0] && y2 >= e[a].mx[1];
}
bool jud_out(int x1, int y1, int x2, int y2, int a)
{
return x1 > e[a].mx[0] || y1 > e[a].mx[1] \
|| x2 < e[a].mn[0] || y2 < e[a].mn[1];
}
bool jud_point(int x1, int y1, int x2, int y2, int a)
{
return x1 <= e[a][0] && y1 <= e[a][1] \
&& x2 >= e[a][0] && y2 >= e[a][1];
}
void jud_res(PII& res, PII a)
{
res.FIR = min(res.FIR, a.FIR), res.SEC = max(res.SEC, a.SEC);
}
PII query(int x1, int y1, int x2, int y2, int a = root)
{
if (!a) return PII(INF, -INF);
if (jud_in(x1, y1, x2, y2, a)) return PII(e[a].mnd, e[a].mxd);
if (jud_out(x1, y1, x2, y2, a)) return PII(INF, -INF);
PII res(INF, -INF);
if (jud_point(x1, y1, x2, y2, a))
jud_res(res, PII(e[a].data, e[a].data));
jud_res(res, query(x1, y1, x2, y2, e[a].l));
jud_res(res, query(x1, y1, x2, y2, e[a].r));
return res;
}
int main (int argc, char const* argv[])
{
IN(A), IN(B), IN(n);
for (int i = 1, k, tot = 1; i <= A; i += 1)
for (int j = 1; j <= B; j += 1)
IN(k), arr[tot++] = (N){i, j, k, k, k};
root = Build(1, A * B);
for (int i = 1; i <= A - n + 1; i += 1)
for (int j = 1; j <= B - n + 1; j += 1)
{
PII tmp = query(i, j, i + n - 1, j + n - 1);
ans = min(ans, tmp.SEC - tmp.FIR);
}
printf("%d\n", ans);
return 0;
}
不甘心的我继续研究暴力方法。
于是想到了这个思路:
首先设 mx[i,j]表示第 i行的区间 [j,j+n−1]内的最大值。
而 mn[i,j]表示第 i行的区间 [j,j+n−1]内的最小值。
即:
mx[i,j]=MAX(a[i,k]|k∈[j,j+n−1])
mn[i,j]=MIN(a[i,k]|k∈[j,j+n−1])
怎么求 mx,mn呢?
不难想到用一颗平衡树维护
首先枚举行数 i,然后把第 i行的区间 [1,n]内的元素丢到平衡树里,这样 mx[i,1]就是平衡树中最大的元素,mn[i,1]就是平衡树中最小的元素。
接着枚举 j,每次将第 i行第 j−1个元素从平衡树中删除,再向平衡树中添加第 i行地 j+n−1个元素。然后依然是平衡树中最小的元素是 mn[i,j],最大的是 mx[i,j]
求出了 mn,mx又有什么用呢?
我们再设 min[i,j]为矩形 (i,j)(i+n,j+n)(这里给出的两个坐标表示矩形的左上角和右下角)内的最小值,max[i,j]表示矩形 (i,j)(i+n,j+n)内的最大值
很明显:
max[i,j]=MAX(mx[k,j]|k∈[i,i+n−1])
min[i,j]=MIN(mn[k,j]|k∈[i,i+n−1])
所以求法和求 mx,mn是相同的!
用平衡树维护即可
复杂度是 O(ABlog2n)的。
但是我,,,很懒,就不想手打平衡树
然后发现 multiset 很慢(map 也很慢),因为它插入、删除、取最大最小都是很慢的。最大的点开 O2 还要 3s+
所以我们用堆来替换它
搞两个堆,一个大根堆一个小根堆。
至于删除,就搞两个哈系表分别对应两个堆,分别储存某个数字被删除了多少次。如果某次取出的堆顶,发现它被删除次数大于 0, 就直接把它弹掉,并且哈系表中它对应的映射值减一。
就这样可以快很多。
堆我用的是 stl 的优先队列,哈希表用的是 pbds 的 gp_hash_table。
我试了试发现 pbds 里也有优先队列,跑的快一些。但是,,,没有本质上快很多,所以就懒得改了。。。
BZOJ 上可以直接过,6s
luogu 上的话时间严格些,需要开 O2 才能过
谁叫我写的暴力呢 QvQ
代码:
Update
突然发现自己制杖了
明明可以用单调队列替换掉优先队列的
QvQ
这样复杂度就是 O(AB)的了
代码:
7 条评论
boshi · 2018年2月10日 10:20 下午
这个能不能用树状数组:O(ABlogN) 或者 RMQ:O(AB) 来搞呢?开了 O2 树状数组应该比较快吧?
konnyakuxzy · 2018年2月10日 10:50 下午
啊
ST 表好像是可以的
但是
我。。。
可能不会
(我写过的那种 RMQ 貌似是预处理要 O(nlog2n)的啊)
真是让人因式分解 QvQ
树状数组的话就不清楚了 QvQ
您是打算用树状数组维护区间最小咩?
那玩意貌似是 log22的吧
boshi · 2018年2月11日 1:43 下午
哦似的似的 log2n,貌似迪卡尔树可以把 RMQ 搞成 O(n) 的虽然我也不懂
TPLY · 2018年2月8日 2:08 下午
konnyakuxzy · 2018年2月8日 2:48 下午
诶,好像是的诶!
貌似没有必要用优先队列的说!
可以把复杂度降到 O(AB)吧
我这就去试试 QvQ
我太蠢了.jpg
(还有您那个,,,原来评论的代码有问题我帮您改好了)
konnyakuxzy · 2018年2月8日 3:44 下午
Orz 我也实现单调队列啦 QvQ
已经更新文章啦 QvQ
TPLY · 2018年2月8日 2:06 下午
诶诶诶这道题我用单调队列水的诶