AtCoder Beginner Contest 438 E,F,G問題メモ

AtCoder Beginner Contest 438

後半は、しっかり実装が求められる問題が多かった印象。

E - Heavy Buckets

問題文

  • $N$ 人の人がおり、$N$ 個のバケツがあります。
  • 人 $i$ ははじめバケツ $i$ のみを持っており、バケツ $i$ には何も入っていません。
  • これから、以下の操作を $10^9$ 回行います。
    • $i = 1, 2, \ldots, N$ について同時に、人 $i$ が持っているバケツすべてに水を $i$ ずつ入れ、それらのバケツを人 $A_i$ に渡す。
  • ただし、バケツに入れることのできる水の量に制限はありません。
  • $i = 1, 2, \ldots, Q$ について以下の形式のクエリに答えてください。
    • $T_i$ 回目の操作の直後にバケツ $B_i$ に入っている水の量を求めてください。

制約

  • $2 \leq N \leq 2 \times 10^5$
  • $1 \leq Q \leq 2 \times 10^5$
  • $1 \leq A_i \leq N$
  • $1 \leq T_i \leq 10^9$
  • $1 \leq B_i \leq N$
  • 入力される値はすべて整数

解法

どの頂点からも $1$ 本ずつ辺が出ているようなグラフを Functional Graph と呼ぶ。

Functional Graph で○○回移動後の状態、というのはダブリングで管理できることが多い。 例えば今回は以下が前計算できると良さそうである。

  • $\mathrm{Owner}[i,t]:=$ バケツ $i$ の $2^t$ 回の操作後の所有者
  • $\mathrm{Water}[i,t]:=$ バケツ $i$ に $2^t$ 回の操作後に入っている水

ただ、Owner(に相当する情報)をダブリングで管理する問題はよく見るが、Waterは見慣れない。 一応、本当にダブリングで管理できるかどうか確認する。

  • 操作回数によって、入る水の量は変わらない。つまり、任意の $x$ について、以下は変わらない
    • 最初に人 $y$ が持っているバケツに、$2^d$ 回の操作後に入っている水
    • $x$ 回の操作後に人 $y$ が持っているバケツに、$x+2^d$ 回の操作後に追加で入っている水
  • 足し算なので、結合法則が成り立つ
    • あるバケツに $t$ 回目の操作で入る水の量を $W_t$ として、たとえば
    • $W_1+W_2+W_3+W_4=(W_1+W_2)+(W_3+W_4)$
    • のように、部分的な集計値同士を足し合わせても結果は変わらない

よって、Waterも問題なく管理できる。前計算は以下のようにおこなえる。計算量は $O(N \log{T})$ となる。

  • $\mathrm{Owner}[i,t+1]=\mathrm{Owner}[\mathrm{Owner}[i,t],t]$
  • $\mathrm{Water}[i,t+1]=\mathrm{Water}[i,t]+\mathrm{Water}[\mathrm{Owner}[i,t],t]$

クエリでは、たとえば $T_i=25=2^0+2^3+2^4$ のように2の冪乗に分解できるとき、 $2^0,2^3,2^4$ 回の操作をそれぞれ一気に進める($t=0,3,4$ についてテーブルを参照する)ことで、1クエリ $O(\log{T})$ で答えを求められる。

Python3

F - Sum of Mex

問題文

  • $N$ 頂点の木 $T$ が与えられます。
  • 各頂点には $0$ から $N-1$ までの番号がついており、$i$ 番目 $(1\le i\le N-1)$ の辺は頂点 $u_i$ と頂点 $v_i$ を双方向に繋いでいます。(頂点番号は 0-indexed で辺番号が 1-indexed であることに注意してください。)
  • $0$ 以上 $N$ 未満の整数の組 $(i,j)$ に対し $f(i,j)$ を以下のように定めます:
    • 木 $T$ の頂点 $i$ から頂点 $j$ までのパスの頂点番号に 含まれない 最小の非負整数
  • 木 $T$ の頂点 $i$ から頂点 $j$ までのパスに頂点 $i,j$ も含まれることに注意してください。
  • $\displaystyle \sum_{0\le i \le j \lt N} f(i,j)$ の値を求めてください。

制約

  • $2\le N\le 2\times 10^5$
  • $0\le u_i \lt v_i \lt N$
  • 入力で与えられるグラフは木
  • 入力される値は全て整数

解法

この数え上げは、主客転倒っぽく、以下の合計として考えられる。

  • 頂点 ⓪ を含むパスの個数
  • 頂点 ⓪① を全て含むパスの個数
  • 頂点 ⓪①② を全て含むパスの個数

⓪を含まないパスはMEXも $0$ なので無視できる。含むパスは最低、$1$ は保証される。
そのうち、①も含んでいたら最低 $2$ は保証されるので、さらにパスの個数だけ $1$ ずつ加算できる。
という考察を繰り返していくと、求める値が上と等しくなることが分かる。

⓪を根として、木DPで以下の情報を求めておく。

  • $C_v:=$ 頂点 $v$ 以下の部分木サイズ
  • $P_v:=$ 頂点 $v$ の親

⓪を含むパス

⓪を端点に持つか、⓪の異なる子以下から2個選ぶ個数として求められる。$C_v$ があれば求められる。

⓪①を含むパス

①から親を辿っていく。いずれ⓪に行き着く。行き着く直前の⓪の子を $v$ とする。

     ⓪
   /|\
 (v) ○ ○
  :  : :
 ↑
 ①
 /\
::

以下の2つから1頂点ずつ選んで結んだパスが、⓪①を含むパスに該当する。

  • ⓪および、⓪の(v)以外の子以下の頂点の個数($C_0-C_v$)
  • ①以下の頂点の個数($C_1$)

2つの積がこの場合の答えとなる。

それ以降

「⓪①$...k$ を含む最短パス」とは、⓪①$...k$ のうちいずれか2つが端点であり、残りがその上に乗っているようなパスを指すとする。
頂点の配置によっては存在しない場合もある。$k$ で存在しなかったら、$k+1$ 以降も当然、存在しない。

$D_k$ を「⓪①$...k$ を含む最短パスの個数」とする。$k=2,3,...$ について、存在する限り、$D_k$ を答えに加算していきたい。

最短パスの端点2つを $a,b$ とすると、(存在すれば)$D_k=C_a \times C_b$ で求められる。
(ただし端点が⓪の時のみ、先ほどの通り $C_v$ を除く必要がある。以降、$C_0$ としたときは除去後の値とする)

最初、$a=0,b=1$ とする。また、最短パス上の頂点を $S$ として記録しておく。$S$ の初期値は先ほど①から親を辿った過程で記録しておく。
$a,b,S$ を管理しつつ $k=2,3,...$ を順次パスに加えた結果を更新していく。

例えば $k=2$ の時を考える。

② が $S$ に含まれるなら、最短パスの端点 $a,b$ は変わらない。$a=0,b=1$ のままである。

それ以外の場合、②からも親を辿っていく。最初に以下のいずれかにぶつかったら止める。

    • $a=2$ に更新される。更新した上で $C_aC_b$ を答えに加える。
    • $b=2$ に更新される。更新した上で $C_aC_b$ を答えに加える。
  • それ以外の $S$ に含まれる頂点
    • ⓪①②を全て含むパスは作れないことが確定する。
    • 当然、それ以降の③,④,…も作れないので、打ち切ってその時点の答えを出力する。

また、⓪か①に到達した場合は、そこまでに通過した頂点も $S$ に加えておく。

③以降も、この繰り返しでよい。つまり、

  • $S$ に含まれていたら直前と同じ
  • 親を辿っていき、$a$ か $b$ に辿り着いたら、辿り着いた方を更新
  • それ以外の $S$ に辿り着いたら打ち切り

としていくと解ける。

最初の木DPでも、後の最短パスを更新していく過程でも、1頂点は高々定数回しか参照されないため、計算量は $O(N)$ となる。

Python3

G - Sum of Min

問題文

  • 整数 $N,M,K$ と長さ $N$ の整数列 $A=(A_0,A_1,\ldots,A_{N-1})$ 、長さ $M$ の整数列 $B=(B_0,B_1,\ldots,B_{M-1})$ が与えられます。添字が $0$ から始まることに注意してください。
  • $\displaystyle\sum_{i=0}^{K-1} \min(A_{i\bmod N}, B_{i \bmod M}) $ を $998244353$ で割ったあまりを求めてください。

制約

  • $1\le N,M\le 2\times 10^5$
  • $1\le K\le 10^{18}$
  • $1\le A_i,B_i\le 10^9$
  • 入力される値は全て整数

解法

ループするindexを $A$ も $B$ も1ずつ進めていくという問題。

とりあえず、後の計算が考えやすくなるので、$\mathrm{GCD}(N,M)=1$ とする。

そうで無い場合は、相対するペアが gcd ごとに飛び飛びになることを利用すれば、{gcd}個の問題に分割できる。
この時、$K$ はそれぞれ $\lfloor \frac{K}{gcd} \rfloor$ に加え、端数は左から順に1ずつ分配される。

N=6  M=9  K=50
A  1  2  3  4  5  6             GCD(6,9)=3
B  1  2  3  4  5  6  7  8  9

Aの(1,4)とペアになりうるのはBの(1,4,7)
Aの(2,5)とペアになりうるのはBの(2,5,8)
Aの(3,6)とペアになりうるのはBの(3,6,9)
↓
以下の3つの問題に分割できる。
K/gcd = 16 あまり 2 なので、分割後の K は、左から2個は17、残りは16となる。

N=2 M=3 K=17    N=2 M=3 K=17    N=2 M=3 K=16
A  1  4         A  2  5         A  3  6
B  1  4  7      B  2  5  8      B  3  6  9

以降、$\mathrm{GCD}(N,M)=1$ とする。

各 $i$($A$ 側のindex)ごとに、ペアとなる $j$($B$ 側のindex)は、以下のようになる。

N=7  M=5  K=60

A  0  1  2  3  4  5  6
B  0  1  2  3  4

   →順にペアとなるj
i↓                                     ペアになる順
 0:  0  2  4  1  3  0  2  4  1 | 3 ...    1  8 15
 1:  1  3  0  2  4  1  3  0  2 | 4 ...    2  9 :
 2:  2  4  1  3  0  2  4  1  3 | 0 ...    3 10 :
 3:  3  0  2  4  1  3  0  2  4 | 1 ...    4 11
 4:  4  1  3  0  2  4  1  3 ,--' 2 ...    5 12
 5:  0  2  4  1  3  0  2  4 |1   3 ...    6 13
 6:  1  3  0  2  4  1  3  0 |2   4 ...    7 14
                         K=60はここまで

以下の3つのフェイズ別に答えを求める。(用語は適当。後で説明)

  • 完全ループ
  • 部分ループ
  • 端数

完全ループ

$NM$ 回の操作後、ペアが最初に戻り、ループする。これを“1完全ループ”とする。
完全ループする過程で、$\{A_0,...,A_{N-1}\}$ は、それぞれ $\{B_0,...,B_{M-1}\}$ と1回ずつペアになる。

   →順にペアとなるj
i↓  _____________
 0: |0  2  4  1  3| 0  2  4  1 | 3 ...
 1: |1  3  0  2  4| 1  3  0  2 | 4 ...
 2: |2  4  1  3  0| 2  4  1  3 | 0 ...
 3: |3  0  2  4  1| 3  0  2  4 | 1 ...
 4: |4  1  3  0  2| 4  1  3 ,--' 2 ...
 5: |0  2  4  1  3| 0  2  4 |1   3 ...
 6: |1  3  0  2  4| 1  3  0 |2   4 ...
     ~~~~~~~~~~~~~
      1完全ループ

よって、1完全ループの寄与は、ソートした $B$ 上で各 $A_i$ を二分探索して、 「$A_i$ 以下の $B_j$ の総和」+「$A_i \times$($A_i$ より大きい $B_j$ の個数)」として求められる。

ループ回数だけ倍加して、答えに加算する。

以降、$K←(K \mod {NM})$ に更新する。

部分ループ

$B$ での位置はずれていてもよいが、$A$ が1周するまでを“1部分ループ”とする。ここが一番テクニカル?

   →順にペアとなるj
i↓                 _______
 0:  0  2  4  1  3 |0  2  4| 1 | 3 ...
 1:  1  3  0  2  4 |1  3  0| 2 | 4 ...
 2:  2  4  1  3  0 |2  4  1| 3 | 0 ...
 3:  3  0  2  4  1 |3  0  2| 4 | 1 ...
 4:  4  1  3  0  2 |4  1  3|,--' 2 ...
 5:  0  2  4  1  3 |0  2  4||1   3 ...
 6:  1  3  0  2  4 |1  3  0||2   4 ...
                    ~~~~~~~
                   3部分ループ

部分ループ回数を、$d = \lfloor \frac{K}{N} \rfloor$ とする。上例では $d=3$ である。

まず、$A_0$ は $d=3$ 回の部分ループ中で $B_0,B_2,B_4$ とペアになる。
この中だけで、完全ループの時のように「$A_i$ 以下の $B_j$ の総和」+「$A_i \times$($A_i$ より大きい $B_j$ の個数)」を求めたい。

$A$ と $B$ に出てくる値で座標圧縮した上で、 有効な $B_i$ の「個数」と「総和」をFenwickTreeなどで管理すれば、求められる。

次 $A_1$ を求めようとすると、$B_1,B_3,B_0$ とペアになるので、$A_0$ の時からFenwickTreeの多くの要素について入れ替わりが発生してしまい、更新コストが大きくなる。
一方、$A_2$ なら、$B_2,B_4$ は保った上で、$B_0$ を除いて $B_1$ を追加するだけで更新できる。

このように、$t=0,1,...,M-1$ について、$i=tN \mod{M}$ とした $i$ の答えを求めていくようにすると、 FenwickTreeの更新箇所は1回につき $O(1)$ で抑えられる。

$N \gt M$ の場合、$i=0$ のタイミングで、$i=5$ など $i+nM$ で表される $i$ についても同時に求められる。

これで、全体 $O(N \log{N})$ などで総和が求められる。

端数

あとは愚直に計算しても $O(N)$ である。

   →順にペアとなるj
i↓                          _
 0:  0  2  4  1  3  0  2  4 |1|| 3 ...
 1:  1  3  0  2  4  1  3  0 |2|| 4 ...
 2:  2  4  1  3  0  2  4  1 |3|| 0 ...
 3:  3  0  2  4  1  3  0  2 |4|| 1 ...
 4:  4  1  3  0  2  4  1  3 ,--' 2 ...
 5:  0  2  4  1  3  0  2  4 |1   3 ...
 6:  1  3  0  2  4  1  3  0 |2   4 ...

Python3

programming_algorithm/contest_history/atcoder/2025/1227_abc438.txt · 最終更新: by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0