HDU-4456 Crowd 二维树状数组+坐标转换
题意:给定一个N*N的网格,现在M组操作,一种操作时改变网格上的某个单点的权值,另外一种操作是求到一点曼哈顿距离为小于等于k的所有的权值和,初始化网格所有点的权值为0。
解法:这题如果没有那些特定的条件,那么就是一个纯净的二维树状数组。对于题目中的要求需要解决的两个问题是:如何将题目中的曼哈顿距离转化为规则的矩形,以及如何避免开辟一个巨大的数组。
1.如何转化为矩形问题,这里处理的方法就是把所有的点都旋转45度,需要一个2N*2N的数组数组才能容下坐标转换之后的图,坐标的转化方式为nx = x - y + N, ny = x + y - 1。可以这样看,转化之后的同行相邻两列的坐标差为(-1, 1), 相邻两列相邻两行的坐标差为(1, 1),所以如果以(0, 0)为参考点(即该点转化前后位置不发生变化)的那么(x, y)就变为(x-y, x+y),又因为所有的点都应该在这个2N*2N的矩形中,因此我们给(1,1)这个点衡坐标加上N,纵坐标-1是为了让最靠左边的点的列从1开始,这样转化后(1,1)->(N,1), (1,N)->(1, N), (N, 1)->(2N-1, N), (N, N)->(N, 2N-1)。
2.如何避免开一个大的数组,由于转化之后的数组达到了20000*20000,这个数组我memset后电脑就死机了,神奇的是编译的时候并没有提示该数组过大。一种有效的方法是将二维的坐标hash处理,即找一个大的质数,每次的更新都选择一个合适的一维数组中的数来表示一个二维中的数,有一个地方要特别注意就是查询的时候并不需要hash处理,如果没有某个数对应的hash值话直接就是0了。
感谢 HDU xiaotao的指点,分析了代码后才恍悟此题的精妙。HDU-4312同样存在这样的一个坐标的转化过程,有兴趣的可以去做做。
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> #include <map> using namespace std; const int MOD = 4073989; int N, M, LIM; int to[MOD]; int bit[MOD]; int hash(int x) { int key = x % MOD; for (int i = key; ; i++) { if (i == MOD) i = 0; if (to[i] && to[i] != x) {} else { to[i] = x; return i; } } } int search(int x) { int key = x % MOD; for (int i = key; ; i++) { if (i == MOD) i = 0; if (to[i] && to[i] != x) {} else { // 查询并不需要给出一个离散化之后的值 return i; } } } inline int lowbit(int x) { return x & (-x); } void add(int x, int y, int val) { for (int i = x; i <= LIM; i += lowbit(i)){ for (int j = y; j <= LIM; j += lowbit(j)) { bit[hash(i*LIM+j)] += val; } } } inline void get(int x, int y, int &rx, int &ry) { rx = x - y + N; ry = x + y - 1; } int sum(int x, int y) { int ret = 0; for (int i = x; i; i -= lowbit(i)) { for (int j = y; j; j -= lowbit(j)) { ret += bit[search(i*LIM+j)]; } } return ret; } int main() { while (scanf("%d", &N), N) { scanf("%d", &M); LIM = N << 1; memset(to, 0, sizeof (to)); memset(bit, 0, sizeof (bit)); int x, y, z, p; int rx, ry, x1, y1, x2, y2; for (int i = 0; i < M; ++i) { scanf("%d%d%d%d", &p, &x, &y, &z); get(x, y, rx, ry); if (p == 1) { add(rx, ry, z); } else { x1 = max(1, rx-z); y1 = max(1, ry-z); x2 = min(LIM, rx+z); y2 = min(LIM, ry+z); printf("%d\n", sum(x2, y2)-sum(x1-1, y2)-sum(x2, y1-1)+sum(x1-1,y1-1)); } } } return 0; }