JPRSプログラミングコンテスト2026#1(AtCoder Beginner Contest 442)E,F,G問題メモ

E - Laser Takahashi

問題文

  • 二次元平面上に $N$ 体のモンスターがいます。モンスター $i$ がいる場所の座標は $(X_i,Y_i)$ です。
    • なお、各モンスターは静止した点としてみなせるものとします。すなわち、モンスターは大きさを持ちません。
  • この平面上の原点には高橋君が立っており、高橋君の目からは強力なレーザーが常に照射されています。
    • 高橋君が向いている方向に存在するモンスターは、何体重なっていても全てが即座に消滅します。
  • 青木君は、$Q$ 個の 独立な 思考実験を行なっています。$j$ 個目の思考実験は以下のようなものです。
    • はじめ、高橋君はモンスター $A_j$ がいる方向を向いている。
    • 今から高橋君は 時計回り に回転を行い、モンスター $B_j$ がいる方向を向いた瞬間に停止する。
    • このとき、(モンスター $A_j,B_j$ を含め)合計で何体のモンスターが消滅するか?
    • なお、モンスター $A_j,B_j$ が原点から見て同じ方向に存在する場合、高橋君は一切回転しない。
  • 各思考実験に対する答えを求めてください。

制約

  • $2\leq N \leq 2\times 10^5$
  • $1\leq Q \leq 2\times 10^5$
  • $-10^9\leq X_i,Y_i \leq 10^9$
  • $(X_i,Y_i)\neq (0,0)$
  • $1\leq A_j,B_j\leq N$
  • $A_j\neq B_j$
  • 入力は全て整数

解法

でじこのコスプレをした高橋君を想像した人は居ませんか?居ませんね。

偏角で時計回りにソートしておけば、モンスター $A_j,B_j$ に対応する角度を $L_j,R_j$ として、 「$L_j$ から $R_j$ までにあるモンスターの数はいくつ?」という単なる累積和の問題になる。

            |
       ⑨   |  ⑩
            |  
---⑧--⑦---+----①------
            |        ②
     ⑥    ⑤  ③
            |    ④
           ↓
                   ③       ⑦
時計回り順   ① ② ④ ⑤ ⑥ ⑧ ⑨ ⑩
累積個数   0  1  2  4  5  6  8  9 10

③から⑨までの間にあるのは、9 - 2 = 7個

偏角を求めるには、多くの言語で atan2(y, x) などが使えるが、 今回の制約のように座標が $10^9$ まであると、精度的に足りなくなる。 (ワンチャン行けないかと特攻したけどやっぱりWAになった。くそぅ)
たとえば、$(1,10^9)$ と $(1,10^9-1)$ の atan2 の結果はいずれも $1.5707963257948967$ となり、区別できなくなる。

座標が整数なら、外積を使えば整数のまま比較できる。
偏角は一般的には反時計回りが標準なので、今回は逆順にソートする。

def angle_sort(p0, p1):
    if p0[0] * p1[1] == p0[1] * p1[0]:
        return 0
    return 1 if p0[0] * p1[1] < p1[0] * p0[1] else -1

monsters = [[1, 2], [2, 1], [0, 5]]  # [x, y] のリスト

monsters.sort(key=cmp_to_key(angle_sort), reverse=True)

ただしこの方法は180度以上離れた2点の順序を正しく判定できない。平面を180度ずつに分けてからソートする必要がある。
座標を「$y \gt 0$ または $y=0,x \gt 0$」の場合と「それ以外」でまず分けた後、それぞれで偏角ソートし、後で結合すればよい。

  • 前準備
    • 各モンスターにつき、$X_i,Y_i$ をGCDで割り、角度が同じモンスターは同じ $(x,y)$ になるように標準化しておく。
    • 標準化後のユニークな $(x,y)$ の個数を $n$ 個とする。
    • $(x,y)$ ごとにモンスターの数を集計し、$c_{x,y}$ とする。
  • 偏角ソート
    • $(x,y)$ を2グループに分け偏角ソートした後、結合する。
  • 累積和の計算
    • 長さ $2n$ の配列 $A$ を用意する。
    • $(x,y)$ が偏角ソート後 $i$ 番目にあたる場合、$A_i$ と $A_{i+n}$ に $c_{x,y}$ を加算する。
    • $A$ の累積和を取る。

こうすれば、クエリでは区間和を求める問題に落とし込める。

Python3

F - Diagonal Separation 2

問題文

  • $N$ 行 $N$ 列のグリッドがあります。グリッドの上から $i$ 行目、左から $j$ 列目のマスをマス $(i, j)$ と表記します。
  • グリッドの各マスは白(.)または黒(#)に塗られています。
  • あなたは、いくつかのマスの色を塗り替えて以下の条件をともに満たすようにします。
    • 全ての行について、「....####」のように左から何個かが白、残りが黒。(白・黒が0マスでもよい)
    • 全ての列について、「....####」のように上から何個かが白、残りが黒。(白・黒が0マスでもよい)
  • 条件を満たすために塗り替える必要のあるマスの数として考えられる最小値を求めてください。

制約

  • $1 \leq N \leq 5000$
  • $N$ は整数
  • $S_i$ は ., # からなる長さ $N$ の文字列

解法

条件を満たすグリッドは、以下のように、行が下になるにつれ白の個数が広義単調減少していくような形であればよい。

....##
...###
...###
.#####
######

よって、「ある行の左から $k$ 個を白で塗れるか、塗れないか」は、その1つ上の行で白を何個塗ったかさえわかれば、判定できる。

以下のDPで求められる。

  • $\mathrm{DP}[i,j]:=i$ 行目の左から $j$ 個を白、残りを黒で塗る場合に、$1~i$ 行目で塗り替える必要のある最小マス数

$i$ 行目を処理する場合を考える。
$S_i$ を左右から調べて、以下を求める。

  • $p_j:=$ 左から $j$ 個を白、残りを黒とする場合に、行内で塗り替える必要のあるマス数($0 \le j \le N$)

$\mathrm{DP}[i,j]$ に遷移できるのは $\mathrm{DP}[i-1,j以上]$ のいずれかからである。 当然、この中の最小値から遷移するのが最適である。

$\mathrm{DP}[i-1,*]$ について、末尾から累積最小値を取っておき、$m_j$ とすると、

  • $\mathrm{DP}[i,j]=p_j+m_j$

で求められる。

Python3

G - Lightweight Knapsack

問題文

  • $N$ 種類のアイテムがあります。
  • $i$ 種類目のアイテムの 重み は $W_i$、価値 は $V_i$ であり、あなたはこれを $K_i$ 個持っています。
  • これらの $K_1+\dots+K_N$ 個のアイテムの中から、重みの総和が $C$ を超えないようにいくつか($0$ 個でもよい)を選ぶとき、選んだアイテムの価値の総和が最大でいくつになるか求めてください。

制約

  • $1\leq N \leq 2\times 10^5$
  • $1\leq C \leq 2\times 10^9$
  • $1\leq W_i \leq 3$
  • $1\leq V_i \leq 10^9$
  • $1\leq K_i \leq 10^9$
  • 入力は全て整数

解法

ナップサックのバリエーションの1つ。$C$ も $V$ も大きいが、$W$ が非常に小さい。

こういう場合、最適解で採用するアイテムのほとんどは、 重さ当たりの価値($V_i/W_i$、以下「密度」と呼ぶ)が高いものから貪欲に埋めていったものと一致する。 最後に微調整すればよい。

微調整では、例えば「貪欲の結果 $C$ が1余ったけど、採用した $W_i=3$ のアイテムを除外して 代わりに次善の $W_j=2$ のアイテムを2個採用した方がよい」みたいなことを調べたい。
どこまで調べれば、見落としが無いことを保証できるか?

重さ別に、

  • 採用したものから、密度の低いもの最大 $2$ 個ずつ(採用を取り消して)
  • 採用してないものから密度の高いもの最大 $2$ 個ずつ

の計12個のアイテムの採用有無を全探索すれば通った。

個数は重さ別に数える必要がある。単に「密度の下位・上位20個」とかだけでは判断できない。
同様に、「途中まで貪欲で、残容量が少なくなったらDP」とかも正しくならない可能性がある。

$2$ 個という探索範囲は、実装したときは証明できた気でいたが、よく考えるとできてなかった。 改めての証明は意外と大変だった。

略証

重さの最大値の一般化に対する考察

全探索部分は、検討アイテム数 $m=12$ として、$O(2^m)$ や、 容量「(貪欲での残容量)$+$(貪欲解から一時的に取り除いたアイテムの重さ)」の普通のナップサックをおこなって求められる。

Python3

解法2

「重さが同じものの中では、価値の高い順に貪欲してよい」ことを主眼に置く。

$\mathrm{LCM}(1,2,3)=6$ なので、重さ別に、価値の高い方から重さが $6$ となるようにグループ化する。

        価値降順
重さ1   [9  9  8  7  5  5] 4  3  3  2  2  
重さ2   [9  7  7][7  6  4] 4  2           [ ]: 1グループ
重さ3   [8  5][4  3][3  1]

こうすると、1つのグループが同じ重さを持ち、全グループを総合して、価値が高い方から貪欲に使用してよくなる。

ただ、これで貪欲すると、グループ化しない貪欲と同様、最後に困ったことが発生する。
「最後に容量が $1$ だけ余った。最後に採用した [1*6] グループをバラして、重さ1を1個取り出し、代わりに重さ2を入れる」 みたいなのが最適になる可能性もあるため。

これは、「重さ $w$ 毎に、グループに属さないがバラで採用する個数を固定する」ことで、一意に定まる。
言い換えれば、重さ $1$ なら(使用個数 $\mod{6}$)、 重さ $2$ なら(使用個数 $\mod{3}$)、重さ $3$ なら(使用個数 $\mod{2}$)をそれぞれ固定する。(36通り)

貪欲後、バラで採用するアイテムの総重量が空き容量に確保できるまで、最後に採用したグループから取り出す。
その後、重さ別に、価値が高い方から固定した個数だけ採用すればよい。

36通り試した最大値が答え。

今回の場合、$K_i,C$ が大きいためグループを陽に持てないので、グループ化の部分の実装が難しそう。

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