【AtCoder】ABC 461 E - E-liter

E - E-literatcoder.jp favicon

実行時間制限: 2 sec / メモリ制限: 1024 MiB / Difficulty: 1431 / NoviSteps: 1D / 配点: 475

問題概要

N×NN \times N のグリッドがあり、はじめ、すべてのマスは白く塗られている。

QQ 回のクエリを与えられる順に処理せよ。 各クエリは以下のいずれかである。

  • 1 R: 整数 RR が与えられる。グリッドの上から RR 行目のマスをすべて黒く塗る。
  • 2 C: 整数 CC が与えられる。グリッドの左から CC 列目のマスをすべて白く塗る。

クエリを処理するたびに、処理が完了した時点でのグリッド内で黒く塗られているマスの個数を出力せよ。

制約

  • 1N,Q3×1051 \leq N, Q \leq 3 \times 10^5
  • 1R,CN1 \leq R, C \leq N
  • 入力される値はすべて整数

考察

主方針

以下では、ii 回目の操作を時刻 ii とみなして考える。最初は時刻 00 で、すべてのマスは白い状態であるとする。

クエリごとに実際に各マスの状態を更新していては計算量が O(NQ)O(NQ) となってしまうので、黒マスの個数だけをクエリごとに差分更新していきたいイメージがある。

とあるマス (r,c)(r, c) に注目したとき、このマスの色は

  • rr が最後に塗られた時刻 row_timer\mathrm{row\_time}_r
  • cc が最後に塗られた時刻 col_timec\mathrm{col\_time}_c

を比較し、 row_timer>col_timec\mathrm{row\_time}_r > \mathrm{col\_time}_c なら黒、そうでなければ白になる。 なお、はじめはすべてのマスが白いので、 row_timer=col_timec=0\mathrm{row\_time}_r = \mathrm{col\_time}_c = 0 として扱うことにする。

クエリごとの処理

クエリ1

時刻 tt で行 RR を黒く塗るとする。 操作前の行 RR における黒マスの個数は #{ccol_timec<row_timeR}\# \left\{ c \mid \mathrm{col\_time}_c < \mathrm{row\_time}_R \right\} である。 また、操作後は row_timeR=t\mathrm{row\_time}_R = t となるので、行 RR における黒マスの個数は #{ccol_timec<t}\# \left\{ c \mid \mathrm{col\_time}_c < t \right\} である。

したがって、クエリ1の処理による黒マスの個数の差分は

#{ccol_timec<t}#{ccol_timec<row_timeR}\# \left\{ c \mid \mathrm{col\_time}_c < t \right\} - \# \left\{ c \mid \mathrm{col\_time}_c < \mathrm{row\_time}_R \right\}

と計算できる。

クエリ2

時刻 tt で列 CC を白く塗るとする。 操作前の列 CC における黒マスの個数は #{rrow_timeC<col_timer}\# \left\{ r \mid \mathrm{row\_time}_C < \mathrm{col\_time}_r \right\} である。 また、操作後は col_timeC=t\mathrm{col\_time}_C = t となるので、列 CC における黒マスの個数は 00 である。 したがって、クエリ2の処理による黒マスの個数の差分は

#{rrow_timeC<col_timer}- \# \left\{ r \mid \mathrm{row\_time}_C < \mathrm{col\_time}_r \right\}

と計算できる。

データ構造の決定と実装

ここまでの話をまとめると、結局やりたいことは row_time\mathrm{row\_time}col_time\mathrm{col\_time} について、

  • ある値 xx よりも小さい / 大きい値を持つ要素数の取得
  • クエリごとの値の1点更新

を高速に行うということになる。

そこで、 Fenwick Tree を用いて、 row_time\mathrm{row\_time}col_time\mathrm{col\_time}値の分布を管理することにする。具体的には、

  • row[t]row_timer=t\mathrm{row\_time}_r = t となる rr の個数
  • col[t]col_timec=t\mathrm{col\_time}_c = t となる cc の個数

とする。 これは、ACLのfenwick_treeを用いると、これらの処理をそれぞれ O(logQ)O(\log Q) の計算量で行うことができる。

例えば、col.sum(0, x)なら col_timec<x\mathrm{col\_time}_c < x となる列数を取得できるし、N - row.sum(0, x+1)なら row_timer>x\mathrm{row\_time}_r > x となる行数を取得できる。

また、以下の処理で行 RR の時刻を tt に更新することができる。

CPP
1.row.add(row_time[R], -1);
2.row_time[R] = t;
3.row.add(row_time[R], 1);

以上より、全体として O(QlogQ)O(Q \log Q) の計算量で解くことができた。

実装例

CPP
1.#include <bits/stdc++.h>
2.using namespace std;
3.
4.#if __has_include(<atcoder/all>)
5.#include <atcoder/all>
6.using namespace atcoder;
7.#endif
8.
9.#define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++)
10.
11.using ll = long long;
12.
13.// ======================================== //
14.
15.int main()
16.{
17. int N, Q;
18. cin >> N >> Q;
19.
20. fenwick_tree<ll> row(Q + 1), col(Q + 1);
21. vector<int> row_time(N + 1, 0), col_time(N + 1, 0);
22.
23. row.add(0, N);
24. col.add(0, N);
25.
26. ll ans = 0;
27. int time = 0;
28. while (Q--)
29. {
30. int t;
31. cin >> t;
32. time++;
33.
34. if (t == 1) {
35. int R;
36. cin >> R;
37.
38. ans += col.sum(0, time) - col.sum(0, row_time[R]);
39. row.add(row_time[R], -1);
40. row_time[R] = time;
41. row.add(row_time[R], 1);
42. }
43. else {
44. int C;
45. cin >> C;
46.
47. ans -= (N - row.sum(0, col_time[C] + 1));
48. col.add(col_time[C], -1);
49. col_time[C] = time;
50. col.add(col_time[C], 1);
51. }
52.
53. cout << ans << endl;
54. }
55.
56. return 0;
57.}
atcoder.jp favicon

実装時間: 45分

コメント

Fenwick Tree に各行・列の時刻の分布を載せるというのが、割と難しい発想だった。