−目次
AtCoder Beginner Contest 356 D,E,F,G問題メモ
D - Masked Popcount
問題文
- 整数 N,M が与えられるので、 N∑k=0 popcount(k&M) を 998244353 で割った余りを求めてください。
- ただし、 & はビット単位 AND 演算を表します。
制約
- N は 0 以上 260 未満の整数
- M は 0 以上 260 未満の整数
解法
2進数で桁毎に考える。
M で'1'が立っている桁において、 k でも'1'が立っているような 0≤k≤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≤k≤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=(A1,…,AN) が与えられます。
- N−1∑i=1N∑j=i+1⌊max(Ai,Aj)min(Ai,Aj)⌋ を求めてください。
- ただし、⌊x⌋ は x 以下の最大の整数を表します。例えば、⌊3.14⌋=3、⌊2⌋=2 です。
制約
- 2≤N≤2×105
- 1≤Ai≤106
解法
Ai が大きい方(分子)になるような全ての (j,i) ペアの答えを求めたい。(ひとまず、Ai は 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となる Aj=5 は、ちゃんと「商1以上~商4以上」の4回でそれぞれ足されている。A の中に2個あるので、2個ずつ足されている。
商を1段ずつに分解していると考える。
これ、商が小さいうちは、横に見て累積和で管理した方が効率がよい。
一方、商が大きい時は、対象範囲が同じものが続くので、縦にまとめて合計した方がよい。
なので、r=⌊√Ai⌋ として、商 r までは横に、以降の部分は縦に見るようにすると、 Ai あたり計算量 O(r) で答えが求められる。
以下を前計算しておくと、
- CNT[i]=A の中の i の個数
- ACC[i]= ↑の累積和
k=1~r についてそれぞれ横と縦で足し合わせて、重複する長方形部分を引けばよい。
- 横に合計: ACC[Ai//k]
- 縦に合計: CNT[k]×(Ai//k)
- 重複部分: ACC[r]×r
O(N√Amax) となる。
なお、A に同じ値が複数ある場合は、余分なペアを除く必要がある。
同じ値が c 個あるような Ai それぞれで、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≤Q≤2×105
- 0≤K≤1018
- 各クエリにおいて、1≤x≤1018
解法
数直線をイメージする。
クエリ1で数直線上の x の位置にコマを置いたり除いたりする。
K 以下の距離にあるコマ同士は同じグループとしたとき、クエリ2では、指定されたコマの属するグループサイズを答える。
そう考えると、各グループは数直線上で連続した区間のコマとなる。
順序を保ったまま、要素を挿入・削除・二分探索できる SortedSet があれば、状態を管理できる。
- ALL:現在の全てのコマの位置の集合を管理するSortedSet
- LEFT:現在の「各グループで最も左端のコマ」の位置の集合を管理するSortedSet
- RIGHT:現在の「各グループで最も右端のコマ」の位置の集合を管理するSortedSet
クエリ1では、x を挿入(削除)することで両隣のコマ l,r とのグループ関係がどうなるかを、丁寧に場合分けして更新する。
(別々だったグループが統合される、左端だけ・右端だけ伸びる、x 単体の新たなグループができる、など)
更新には l,x,r の情報だけがあればよく、l よりさらに左のコマなどは知らなくても更新できるのがポイント。
クエリ2では、LEFT,RIGHT を二分探索することで x の属するグループの左端・右端がわかるので、 それぞれのALL上の位置を求めることで要素数が分かる。
Python は sortedcontainer がライブラリに入ったので、こういった問題がかなり解きやすくなった。
G - Freestyle
問題文
- 高橋くんは N 種類の泳ぎ方ができます。
- 高橋くんが i 種類目の泳ぎ方で泳ぐと、 1 秒あたり体力を Ai 消費して Bi [m] 進みます。
- Q 個のクエリに答えてください。そのうち i 個目は次の通りです。
- 消費する体力の合計を Ci 以下にして Di [m] 進むことができるか判定し、進める場合は必要な最小の秒数を求めよ。
- ただし、高橋くんは泳ぎ方を自由に組み合わせることができ、泳ぎ方を変える体力と時間は無視できます。
- ※各泳ぎ方で泳ぐ時間は小数でもよい
制約
- 1≤N≤2×105
- 1≤Ai,Bi≤109
- 1≤Q≤2×105
- 1≤Ci,Di≤109
解法
泳ぎ方の点 (Ai,Bi) をグラフにプロットして、意味を考えてみる。
「原点から、各泳ぎ方の点へのベクトル」が、1秒間、その泳ぎ方で進むことを表す。
2秒、3秒、泳いだらベクトルが2倍、3倍に伸びていくイメージ。
「ベクトルの傾き」が体力効率を示す。
各泳ぎ方の点の y 座標が速度(時間効率)を示す。
クエリで秒数を最小化する上ではなるべく速い(高い位置にある)泳ぎ方を使いたい。
泳ぎ方の点同士を結ぶ線分上の点は、「1秒間内でその2つの泳ぎ方を組み合わせる」ことを表す。
体力効率を上げる代わりに速度を落とす(または逆)みたいな選択ができる。
泳ぐ時間は小数でもよいので、線分上の任意の泳ぎ方ができる。
3つ以上の泳ぎ方を組み合わせるのは、「i,j を結ぶ線分上の点と、別の泳ぎ方の点 k の間」に
同様に線分を引くことを繰り返せば表現できる。
(結果的に、3つ以上を組み合わせた泳ぎ方は、下記の“意味のある泳ぎ方”になることはないが)
ある泳ぎ方に対し、体力効率も時間効率も劣る泳ぎ方は採用する意味が無いので、意味のある泳ぎ方は凸包みたいな形になる。
ただし、上側凸包でよく、さらに y 座標は狭義単調増加する範囲で良い。
まずこの部分的な凸包を構築する。
クエリが来たら、
- 最大体力効率より高い体力効率を求められるクエリは不可
- 意味のある泳ぎ方の中で最低体力効率(必然的に最速)以下の体力効率しか求められないクエリは、最速の泳ぎ方
その間にあるものは、凸包と、原点→クエリ点を結ぶ線の交点が、ちょうどよい体力効率の1秒当たりの泳ぎ方を示す。