F - Count Cells in a Window

F - Count Cells in a Window

Problem Statement

You are given a grid with $N$ rows and $M$ columns. In the $i$-th row from the top, the squares from the $L_i$-th through $R_i$-th columns from the left are painted black, and the other squares are painted white.

You are given $Q$ queries. For each query, answer the following question.

  • You are given integers $A,B,C,D$. Find the number of black squares contained in the rectangular region from the $A$-th through $B$-th rows from the top and from the $C$-th through $D$-th columns from the left.

Constraints

  • $1 \le N,M,Q \le 2\times10^5$
  • $1 \le L_i \le R_i \le M$
  • For each query, $1 \le A \le B \le N$.
  • For each query, $1 \le C \le D \le M$.
  • All input values are integers.

Input

The input is given from Standard Input in the following format:

$N$ $M$ $Q$
$L_1$ $R_1$
$L_2$ $R_2$
$\vdots$
$L_N$ $R_N$
$\mathrm{query}_1$
$\vdots$
$\mathrm{query}_Q$

Each query $\mathrm{query}_i ~ (1 \le i \le Q)$ is given in the form

$A$ $B$ $C$ $D$

Output

Output $Q$ lines. The $i$-th line should contain the answer to the $i$-th query.


Sample Input 1

3 6 3
2 4
1 1
4 6
1 2 1 4
2 3 3 6
1 1 5 6

Sample Output 1

4
3
0

The black squares are located as shown in the figure above. The $1$-st query asks for the number of black squares within the blue rectangle at the upper left, the $2$-nd query within the red rectangle at the lower right, and the $3$-rd query within the green rectangle at the upper right.

Thus, output $4, 3, 0$, respectively.


Sample Input 2

10 20 12
3 8
1 4
12 19
5 14
2 2
9 17
1 20
6 11
16 20
7 7
2 8 4 15
6 10 1 9
3 4 1 4
1 10 1 20
4 9 10 18
5 5 1 20
1 6 8 8
8 10 13 20
2 7 1 5
6 9 6 16
3 10 18 20
7 10 7 12

Sample Output 2

40
15
0
70
27
1
2
5
11
26
8
12

 

解题思路

  由于 $N\times M$ 最大可达 $4\times 10^{10}$,直接预处理二维前缀和显然不行。注意到每一行的黑色格子恰好构成连续区间 $[L_i,R_i]$,因此加入第 $i$ 行等价于在以列为下标的序列上,对区间 $[L_i,R_i]$ 整体加 $1$。顺着这一思路,最直接的做法是用可持久化线段树维护每一列黑色格子的数量。将处理完前 $i$ 行的线段树看作版本 $i$,查询行区间 $[A,B]$、列区间 $[C,D]$ 时,用版本 $B$ 在 $[C,D]$ 上的结果减去版本 $A-1$ 的结果即可。不过这种做法中线段树的修改是区间加,需要用到标记永久化这一技巧,然而博主并不会。实际上我们并不需要可持久化,只需利用其前缀和的可减性,就能得到一个简单的离线做法。

  具体地,我们仍需一棵线段树来维护扫描到当前行为止,每一列上黑色格子的数量。之所以无需保存任何历史版本,是因为行区间 $[A,B]$、列区间 $[C,D]$ 内的黑色格子数,等于前缀矩形 $[1,B]\times[C,D]$ 与 $[1,A-1]\times[C,D]$ 内黑色格子数之差,而这两个前缀查询发生在扫描过程中的不同时刻,最后再作差。于是可以按扫描线的方式离线处理。在读入所有询问后,把每个询问拆成两个事件:扫描完前 $B$ 行后以正号查询一次 $[C,D]$ 的区间和,以及扫描完前 $A-1$ 行后以负号查询同样的值。再将所有事件按行号从小到大排序。扫描时从第 $1$ 行开始,依次把 $[L_i,R_i]$ 在线段树上区间加 $1$。处理到行号为 $x$ 的事件时,前 $x$ 行均已加入,此时查询 $[C,D]$ 的区间和,并按该事件的符号累加到对应询问的答案中。

  AC 代码如下,时间复杂度为 $O\left(q\log{q} + (n+q)\log{m}\right)$:

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

typedef long long LL;

const int N = 2e5 + 5;

int l[N], r[N];
int a[N], b[N], c[N], d[N];
LL ans[N];
struct Node {
    int l, r;
    LL s, add;
}tr[N * 4];

void build(int u, int l, int r) {
    tr[u] = {l, r};
    if (l != r) {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
    }
}

void upd(int u, LL c) {
    tr[u].s += (tr[u].r - tr[u].l + 1) * c;
    tr[u].add += c;
}

void pushdown(int u) {
    if (tr[u].add) {
        upd(u << 1, tr[u].add);
        upd(u << 1 | 1, tr[u].add);
        tr[u].add = 0;
    }
}

void modify(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) {
        upd(u, 1);
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r);
        if (r >= mid + 1) modify(u << 1 | 1, l, r);
        tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
    }
}

LL query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].s;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid) return query(u << 1, l, r);
    if (l >= mid + 1) return query(u << 1 | 1, l, r);
    return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
    }
    vector<array<int, 3>> p;
    for (int i = 0; i < k; i++) {
        cin >> a[i] >> b[i] >> c[i] >> d[i];
        p.push_back({a[i] - 1, i, -1});
        p.push_back({b[i], i, 1});
    }
    sort(p.begin(), p.end());
    build(1, 1, m);
    for (int i = 0, j = 1; i < p.size(); i++) {
        while (j <= p[i][0]) {
            modify(1, l[j], r[j]);
            j++;
        }
        ans[p[i][1]] += p[i][2] * query(1, c[p[i][1]], d[p[i][1]]);
    }
    for (int i = 0; i < k; i++) {
        cout << ans[i] << '\n';
    }
    
    return 0;
}

 

日记

  不得不承认这篇博客更多是为了 $9$ 月份不会空过而发的()最近好多事要做很忙啊,比赛基本就抽空打打 leetcode,已经完全没时间补题加训了。codeforces 更是半年多没打过比赛了,这下终于退坑了说是()不过如果运气好的话 $11$ 月份就可以逐渐恢复成过去的模式,但愿吧。说好的打一辈子算竞呢.jpg。

 

参考资料

  Editorial - UNICORNProgramming Contest2026(AtCoder Beginner Contest 477):https://atcoder.jp/contests/abc477/editorial/26406

posted @ 2026-09-29 21:07  onlyblues  阅读(8)  评论(0)    收藏  举报
Web Analytics