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
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/23162337

浙公网安备 33010602011771号