[SHOI2007] 园丁的烦恼

发布时间 2023-11-19 17:00:00作者: onlyblues

[SHOI2007] 园丁的烦恼

题目背景

很久很久以前,在遥远的大陆上有一个美丽的国家。统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草。

有一天国王漫步在花园里,若有所思,他问一个园丁道: “最近我在思索一个问题,如果我们把花坛摆成六个六角形,那么……”

“那么本质上它是一个深度优先搜索,陛下。”园丁深深地向国王鞠了一躬。

“嗯……我听说有一种怪物叫九头蛇,它非常贪吃苹果树……”

“是的,显然这是一道经典的动态规划题,早在 N 元 $4002$ 年我们就已经发现了其中的奥秘了,陛下。”

“该死的,你究竟是什么来头?”

“陛下息怒,干我们的这行经常莫名其妙地被问到和 OI 有关的题目,我也是为了预防万一啊!” 王者的尊严受到了伤害,这是不可容忍的。

题目描述

看来一般的难题是难不倒这位园丁的,国王最后打算用车轮战来消耗他的实力: “年轻人,在我的花园里有 $n$ 棵树,每一棵树可以用一个整数坐标来表示,一会儿,我的 $m$ 个骑士们会来轮番询问你某一个矩阵内有多少树,如果你不能立即答对,你就准备走人吧!”说完,国王气呼呼地先走了。

这下轮到园丁傻眼了,他没有准备过这样的问题。所幸的是,作为“全国园丁保护联盟”的会长——你,可以成为他的最后一根救命稻草。

输入格式

第一行有两个整数 $n, m$,分别表示树木个数和询问次数。

接下来 $n$ 行,每行两个整数 $x, y$,表示存在一棵坐标为 $(x, y)$ 的树。有可能存在两棵树位于同一坐标。

接下来 $m$ 行,每行四个整数 $a, b, c, d$,表示查询以 $(a, b)$ 为左下角,$(c, d)$ 为右上角的矩形内部(包括边界)有多少棵树。

输出格式

对于每个查询,输出一行一个整数表示答案。

样例 #1

样例输入 #1

3 1
0 0 
0 1
1 0
0 0 1 1

样例输出 #1

3

提示

数据规模与约定

  • 对于 $30\%$ 的数据,保证 $n, m \leq 10$。
  • 对于 $100\%$ 的数据,保证 $0 \leq n \leq 5 \times 10^5$,$1 \leq m \leq 5 \times 10^5$,$0 \leq x, y, a, b, c, d \leq 10^7$,$a \leq c$,$b \leq d$。

 

解题思路

  二维数点问题,就是平面上有若干个点,每次询问某个矩形内有多少个点。

  由二维前缀和知道,定义 $S_{x,y}$ 表示以 $(x,y)$ 为右上角,左下角为原点 $(1,1)$ 的矩形中点的数量。如果矩形的左下角为 $(x_1,y_1)$ 右上角为 $(x_2,y_2)$,那么该矩形中含有的点的数量就是$$S_{x_2 \, , \, y_2} \; - \; S_{x_2 \, , \, y_1-1} \; + \; S_{x_1-1 \, , \, y_2} \; + \; S_{x_1-1 \, , \, y_1-1}$$

  而由于值域很大,因此我们不可能通过平方级别的复杂度预处理出来前缀和 $S$,考虑优化。在前缀和的计算公式中,当询问某个矩形中点的数量时,本质上是拆分成 $4$ 个左下角均为 $(1,1)$,右上角分别为 $(x_2,y_2)$、$(x_2,y_1-1)$、$(x_1-1,y_2)$、$(x_1-1,y_1-1)$ 的矩形计算得到。只考虑这些左下角为 $(1,1)$ 的矩形,如果点 $(x',y')$ 在矩形内,那么就要同时满足 $\displaylines{\begin{cases} 1 \leq x' \leq x \\ 1 \leq y' \leq y \end{cases}}$。

  考虑固定 $x$ 这个约束条件,对这些左下角为 $(1,1)$ 的矩形以 $x$ 为关键字从小到大排序,枚举矩形的同时用树状数组维护点的数量。当枚举到矩形 $(x_i,y_i)$ 时,把所有满足 $x' \leq x_i$ 的点在 $y'$ 上加 $1$,并用树状数组来动态维护前缀和(所有横坐标不超过 $x'$,纵坐标不超过某个值的点的数量)。那么矩形 $(x_i,y_i)$ 内点的数量就是 query(yi)

  所以做法就是把 $m$ 个矩形分别拆分成上述说到的 $4$ 个矩形,用四元组 $(x_i,y_i,c,\text{idx})$ 来表示,其中 $(x_i,y_i)$ 表示矩形的右上角,$c$ 表示该矩形在前缀和公式中的系数($1$ 或 $-1$),$\text{idx}$ 表示来自第几个矩形,那么就会得到 $4m$ 条记录。按照第一个参数 $x$ 从小到大排序,枚举的过程中用树状数组维护横坐标小于 $x_i$ 的点的数量,然后把结果 c * query(yi) 累加到第 $\text{idx}$ 个矩形的结果中。

  一些注意事项,由于坐标可能为 $0$,为了方便这里把所有坐标都加 $1$。同时由于值域 $A \leq 10^7$,内存限制有 $125 \text{ MB}$,所以不需要离散化。

  AC 代码如下,时间复杂度为 $O(n \log{A})$:

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> PII;

const int N = 5e5 + 10, M = 1e7 + 10;

PII p[N];
struct Node {
    int x, y, c, idx;
}q[N * 4];
int tr[M];
int ans[N];

int lowbit(int x) {
    return x & -x;
}

void add(int x, int c) {
    for (int i = x; i < M; i += lowbit(i)) {
        tr[i] += c;
    }
}

int query(int x) {
    int ret = 0;
    for (int i = x; i; i -= lowbit(i)) {
        ret += tr[i];
    }
    return ret;
}

int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%d %d", &p[i].first, &p[i].second);
        p[i].first++, p[i].second++;
    }
    for (int i = 0, j = 0; i < m; i++) {
        int x1, y1, x2, y2;
        scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
        x1++, y1++, x2++, y2++;
        q[j++] = {x2, y2, 1, i};
        q[j++] = {x1 - 1, y1 - 1, 1, i};
        q[j++] = {x2, y1 - 1, -1, i};
        q[j++] = {x1 - 1, y2, -1, i};
    }
    sort(p, p + n);
    sort(q, q + 4 * m, [&](Node &a, Node &b) {
        return a.x < b.x;
    });
    for (int i = 0, j = 0; i < m << 2; i++) {
        while (j < n && p[j].first <= q[i].x) {
            add(p[j++].second, 1);
        }
        ans[q[i].idx] += q[i].c * query(q[i].y);
    }
    for (int i = 0; i < m; i++) {
        printf("%d\n", ans[i]);
    }
    
    return 0;
}

 

参考资料

  【数据结构】二维数点/二维偏序:https://blog.csdn.net/qq_30320171/article/details/129787418