Processing math: 22%

AtCoder Beginner Contest 356 D,E,F,G問題メモ

D - Masked Popcount

問題文

  • 整数 N,M が与えられるので、 Nk=0 popcount(k&M)998244353 で割った余りを求めてください。
    • ただし、 & はビット単位 AND 演算を表します。

制約

  • N0 以上 260 未満の整数
  • M0 以上 260 未満の整数

解法

2進数で桁毎に考える。

M で'1'が立っている桁において、 k でも'1'が立っているような 0kN の全ての k は、1ずつ答えに寄与する。

M    100101

 ..??1?????    ① 0≦k≦N でこんな k の個数

 ..?????1??    ② 0≦k≦N でこんな k の個数

 ..???????1    ③ 0≦k≦N でこんな k の個数
   
答え=①+②+③
(※重複していてもよい、たとえば 100100 は①と②で2回数える)

なので、他の桁のことは気にせず、「d 桁目に1が立つような、0kN となる 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通り

これを、各桁について合計したものが、最終的な答えとなる。

Python3

E - Max/Min

問題文

  • 長さ N の数列 A=(A1,,AN) が与えられます。
  • N1i=1Nj=i+1max を求めてください。
    • ただし、\lfloor x \rfloorx 以下の最大の整数を表します。例えば、\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_iA の中に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} だけ余分に足されている。

Python3

F - Distance Component Size Query

問題文

  • 整数 K が与えられます。はじめ空である集合 S に対して、次の 2 種類のクエリを順に Q 個処理してください。
    • '1 x':整数 x が与えられる。Sx が含まれているならば、S から x を取り除く。そうでないならば、Sx を追加する。
    • '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 がライブラリに入ったので、こういった問題がかなり解きやすくなった。

Python3

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秒当たりの泳ぎ方を示す。

Python3

programming_algorithm/contest_history/atcoder/2024/0601_abc356.txt · 最終更新: 2024/06/11 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0