目次
AtCoder Beginner Contest 356 D,E,F,G問題メモ
D - Masked Popcount
問題文
- 整数 $N,M$ が与えられるので、 $\displaystyle \sum_{k=0}^{N}$ $\rm{popcount}$$(k \& M)$ を $998244353$ で割った余りを求めてください。
- ただし、 $\mathbin{\&}$ はビット単位 $\rm{AND}$ 演算を表します。
制約
- $N$ は $0$ 以上 $2^{60}$ 未満の整数
- $M$ は $0$ 以上 $2^{60}$ 未満の整数
解法
2進数で桁毎に考える。
$M$ で'1'が立っている桁において、 $k$ でも'1'が立っているような $0 \le k \le N$ の全ての $k$ は、1ずつ答えに寄与する。
M 100101 ..??1????? ① 0≦k≦N でこんな k の個数 ..?????1?? ② 0≦k≦N でこんな k の個数 ..???????1 ③ 0≦k≦N でこんな k の個数 答え=①+②+③ (※重複していてもよい、たとえば 100100 は①と②で2回数える)
なので、他の桁のことは気にせず、「$d$ 桁目に1が立つような、$0 \le k \le N$ となる $k$ は何個ある?」が独立に求められればいい。
a) d 桁目より上位の桁で、既に k<N が確定している場合 d N 110110100 k ????1???? 前半の????の列に来るのは、0000~1100 の13通り。(N を d だけ右bitshiftした値) それぞれに対し、後半の????の列には何を置いてもよいので0000~1111の16通りの数が来る。 → 13 * 16 = 208 通り b) d 桁目では k<N は確定してない場合 d N 110110100 k 11011???? d以上の桁はNと一致している。そのうえで k の d 桁目が1なので、 まず、N の d 桁目も 1 である必要がある。 あとは、それより下位の桁でNを超えないような k を数えればよい。 Nの下位d-1桁(????部分)を取り出した値 + 1(0を含むため)が答えとなる。 → 0100 + 1 = 5通り
これを、各桁について合計したものが、最終的な答えとなる。
E - Max/Min
問題文
- 長さ $N$ の数列 $A=(A_1,\ldots,A_N)$ が与えられます。
- $\displaystyle \sum_{i=1}^{N-1}\sum_{j=i+1}^{N}\left\lfloor\frac{\max(A_i,A_j)}{\min(A_i,A_j)}\right\rfloor$ を求めてください。
- ただし、$\lfloor x \rfloor$ は $x$ 以下の最大の整数を表します。例えば、$\lfloor 3.14 \rfloor=3$、$\lfloor 2 \rfloor=2$ です。
制約
- $2 \leq N \leq 2\times 10^5$
- $1 \leq A_i \leq 10^6$
解法
$A_i$ が大きい方(分子)になるような全ての $(j,i)$ ペアの答えを求めたい。(ひとまず、$A_i$ は $A$ の中に1つしか無いとする)
すると、答えは以下のような範囲の合計となる。
例 A = (1 2 2 2 5 5 15 22) Ai=22 について考える 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 個数 1 3 2 1 商1以上 |1--3--------2-----------------------------1---------------------| 商2以上 |1--3--------2------------------| 商3以上 |1--3--------2------| 商4以上 |1--3--------2| 商5以上 |1--3------| 商6以上 |1--3---| 商7以上 |1--3---| 商8以上 |1--3| : 商11以上|1--3| 商12以上|1| : 商22以上|1| 22が分子に来るようなペアにおける商の合計: 1*22 + 3*11 + 2*4 + 1*1 = 64
たとえば商が4となる $A_j=5$ は、ちゃんと「商1以上~商4以上」の4回でそれぞれ足されている。$A$ の中に2個あるので、2個ずつ足されている。
商を1段ずつに分解していると考える。
これ、商が小さいうちは、横に見て累積和で管理した方が効率がよい。
一方、商が大きい時は、対象範囲が同じものが続くので、縦にまとめて合計した方がよい。
なので、$r = \lfloor \sqrt{A_i} \rfloor$ として、商 $r$ までは横に、以降の部分は縦に見るようにすると、 $A_i$ あたり計算量 $O(r)$ で答えが求められる。
以下を前計算しておくと、
- $\mathrm{CNT}[i]=A$ の中の $i$ の個数
- $\mathrm{ACC}[i]=$ ↑の累積和
$k=1~r$ についてそれぞれ横と縦で足し合わせて、重複する長方形部分を引けばよい。
- 横に合計: $\mathrm{ACC}[A_i // k]$
- 縦に合計: $\mathrm{CNT}[k] \times (A_i // k)$
- 重複部分: $\mathrm{ACC}[r] \times r$
$O(N \sqrt{A_{\max}})$ となる。
なお、$A$ に同じ値が複数ある場合は、余分なペアを除く必要がある。
同じ値が $c$ 個あるような $A_i$ それぞれで、$\frac{c(c+1)}{2}$ だけ余分に足されている。
F - Distance Component Size Query
問題文
- 整数 $K$ が与えられます。はじめ空である集合 $S$ に対して、次の $2$ 種類のクエリを順に $Q$ 個処理してください。
'1 x
':整数 $x$ が与えられる。$S$ に $x$ が含まれているならば、$S$ から $x$ を取り除く。そうでないならば、$S$ に $x$ を追加する。'2 x
':$S$ に含まれる整数 $x$ が与えられる。$S$ に含まれる数を頂点とし、差の絶対値が $K$ 以下であるような数の間に辺を張ったグラフにおいて、$x$ が属する連結成分の頂点数を出力する。
制約
- $1 \leq Q \leq 2\times 10^5$
- $0 \leq K \leq 10^{18}$
- 各クエリにおいて、$1\leq x \leq 10^{18}$
解法
数直線をイメージする。
クエリ1で数直線上の $x$ の位置にコマを置いたり除いたりする。
$K$ 以下の距離にあるコマ同士は同じグループとしたとき、クエリ2では、指定されたコマの属するグループサイズを答える。
そう考えると、各グループは数直線上で連続した区間のコマとなる。
順序を保ったまま、要素を挿入・削除・二分探索できる SortedSet があれば、状態を管理できる。
- $\mathrm{ALL}$:現在の全てのコマの位置の集合を管理するSortedSet
- $\mathrm{LEFT}$:現在の「各グループで最も左端のコマ」の位置の集合を管理するSortedSet
- $\mathrm{RIGHT}$:現在の「各グループで最も右端のコマ」の位置の集合を管理するSortedSet
クエリ1では、$x$ を挿入(削除)することで両隣のコマ $l,r$ とのグループ関係がどうなるかを、丁寧に場合分けして更新する。
(別々だったグループが統合される、左端だけ・右端だけ伸びる、$x$ 単体の新たなグループができる、など)
更新には $l,x,r$ の情報だけがあればよく、$l$ よりさらに左のコマなどは知らなくても更新できるのがポイント。
クエリ2では、LEFT,RIGHT を二分探索することで $x$ の属するグループの左端・右端がわかるので、 それぞれのALL上の位置を求めることで要素数が分かる。
Python は sortedcontainer がライブラリに入ったので、こういった問題がかなり解きやすくなった。
G - Freestyle
問題文
- 高橋くんは $N$ 種類の泳ぎ方ができます。
- 高橋くんが $i$ 種類目の泳ぎ方で泳ぐと、 $1$ 秒あたり体力を $A_i$ 消費して $B_i$ [m] 進みます。
- $Q$ 個のクエリに答えてください。そのうち $i$ 個目は次の通りです。
- 消費する体力の合計を $C_i$ 以下にして $D_i$ [m] 進むことができるか判定し、進める場合は必要な最小の秒数を求めよ。
- ただし、高橋くんは泳ぎ方を自由に組み合わせることができ、泳ぎ方を変える体力と時間は無視できます。
- ※各泳ぎ方で泳ぐ時間は小数でもよい
制約
- $1 \le N \le 2 \times 10^5$
- $1 \le A_i,B_i \le 10^9$
- $1 \le Q \le 2 \times 10^5$
- $1 \le C_i,D_i \le 10^9$
解法
泳ぎ方の点 $(A_i,B_i)$ をグラフにプロットして、意味を考えてみる。
「原点から、各泳ぎ方の点へのベクトル」が、1秒間、その泳ぎ方で進むことを表す。
2秒、3秒、泳いだらベクトルが2倍、3倍に伸びていくイメージ。
「ベクトルの傾き」が体力効率を示す。
各泳ぎ方の点の $y$ 座標が速度(時間効率)を示す。
クエリで秒数を最小化する上ではなるべく速い(高い位置にある)泳ぎ方を使いたい。
泳ぎ方の点同士を結ぶ線分上の点は、「1秒間内でその2つの泳ぎ方を組み合わせる」ことを表す。
体力効率を上げる代わりに速度を落とす(または逆)みたいな選択ができる。
泳ぐ時間は小数でもよいので、線分上の任意の泳ぎ方ができる。
3つ以上の泳ぎ方を組み合わせるのは、「$i,j$ を結ぶ線分上の点と、別の泳ぎ方の点 $k$ の間」に
同様に線分を引くことを繰り返せば表現できる。
(結果的に、3つ以上を組み合わせた泳ぎ方は、下記の“意味のある泳ぎ方”になることはないが)
ある泳ぎ方に対し、体力効率も時間効率も劣る泳ぎ方は採用する意味が無いので、意味のある泳ぎ方は凸包みたいな形になる。
ただし、上側凸包でよく、さらに $y$ 座標は狭義単調増加する範囲で良い。
まずこの部分的な凸包を構築する。
クエリが来たら、
- 最大体力効率より高い体力効率を求められるクエリは不可
- 意味のある泳ぎ方の中で最低体力効率(必然的に最速)以下の体力効率しか求められないクエリは、最速の泳ぎ方
その間にあるものは、凸包と、原点→クエリ点を結ぶ線の交点が、ちょうどよい体力効率の1秒当たりの泳ぎ方を示す。