Loading [MathJax]/jax/output/CommonHTML/jax.js

オムロンプログラミングコンテスト2025(AtCoder Beginner Contest 397)E,F,G問題メモ

オムロンプログラミングコンテスト2025(AtCoder Beginner Contest 397)

コンテスト開始数分前から、AtCoderのトップページが500エラーで落ちていた。
コンテストページは無事だったためか、特にアナウンスも無く、普通にRatedだった。参加するコンテストページは直接把握しておいた方が良さそう。
(ABCなら、https://atcoder.jp/contests/abcXXX)

E - Path Decomposition of a Tree

問題文

  • NK 頂点の木が与えられます。頂点には 1,2,,NK の番号がついており、i 番目 (i=1,2,,NK1) の辺は頂点 ui,vi を双方向に結んでいます。
  • この木を N 本の長さ K のパスに分解できるか判定してください。より詳細には、以下を満たす N×K 行列 P が存在するかどうか判定してください。
    • P1,1,,P1,K,P2,1,,PN,K1,2,,NK の並べ替えである。
    • i=1,2,,N,j=1,2,,K1 について、頂点 Pi,j と頂点 Pi,j+1 を結ぶ辺が存在する。

制約

  • 1N
  • 1K
  • NK2×105
  • 1ui<viNK
  • 与えられるグラフは木である
  • 入力はすべて整数

解法

適当な頂点を根として木DP。
DP[v] を、以下を表すものとして定義する。

  • v 以下の部分木は、1本の「v を端点の1つとする長さ DP[v] のパス」と、0本以上の「長さ K のパス」に分解できる

葉は DP[v]=1 である。

子を持つ頂点は、子のDPの値の中に K でない数が含まれる個数で場合分けする。

  • 0 個の時
    • そこから新たなパスを開始できる。DP[v]=1
  • 1 個の時
    • パスを引き継ぐ。その子を ci として、DP[v]=DP[ci]+1
  • 2 個の時
    • v を介して、その2本を折り返す形で繋ぐ場合のみ許される。
    • 2つの子を ci,cj として、DP[ci]+DP[cj]+1=K ならOK。
      • DP[v]=K とする。(v が端点ではないが、後の処理に問題ないので便宜的な措置)
    • その他の場合、不可能。
  • 3 個以上の時
    • 不可能。

不可能にならず、根までDPを完遂できたら可能。

Python3

F - Variety Split Hard

問題文

  • 長さ N の整数列 A=(A1,A2,,AN) が与えられます。
  • A2 か所で区切って 3 個の空でない連続する部分列に分割するとき、数列に含まれる数の種類数の和としてありえる最大値を求めてください。
  • より厳密には、1i<jN1 を満たす整数の組 (i,j) について (A1,A2,,Ai),(Ai+1,Ai+2,,Aj),(Aj+1,Aj+2,,AN) のそれぞれに含まれる整数の種類数の和としてありえる最大値を求めてください。

制約

  • 3N3×105
  • 1AiN (1iN)
  • 入力はすべて整数である

解法

C問題の強化版であると問題冒頭にあったおかげで、F問題と考察をまとめることができた。
完全にコードを流用できるわけではなくても、こういう情報を明示してくれるのはありがたい。

ある数字が k 回(k2)出現するとして、(1,2),(2,3),...,(k1,k) 番目に出てくる箇所をそれぞれ両端とした区間を考える。

    3   1   4   1   5   9   2   6   5   3   5   8   9   7   9
3  (  *   *   *   *   *   *   *   *   *  )
1      (  *   *  )
5                  (  *   *   *   *  )
5                                  (  *   *  )
9                      (  *   *   *   *   *   *   *  )
9                                                  (  *   *  )

ある区間について、そこに含まれる 「*」(数字と数字の間)で部分列を分けた時、区間の両端の数字が別々に数えられる。
逆に、それ以外(区間が部分列に完全に内包される)の場合、両端の数字は必ずダブってしまう。

よって、問題は以下のように言い換えられる。

  • A 全体に含まれる数字の種類数は、必ず確保できる。
  • 各区間について、それを分割する位置で部分列を分けた場合、「分割した区間数」だけ追加ボーナスが入る。
  • 追加ボーナスを最大化せよ。

なるべく多くの区間をぶった切れる箇所で部分列を分割するとよい。

C問題は分割箇所を1つ決めればよいので、重なり数の最大値を取ればよい。

  • C問題の答え = (A 全体に含まれる数字の種類数)+(「*」が重なっている最大数)
    • 下例では、9+3=12
    3   1   4   1   5   9   2 | 6   5   3   5   8   9   7   9
3  (  *   *   *   *   *   *   +   *   *  )
1      (  *   *  )            |
5                  (  *   *   +   *  )
5                             |    (  *   *  )
9                      (  *   +   *   *   *   *   *  )
9                             |                    (  *   *  )

F問題は3つの区間に分割するので分割箇所を2つ決めるが、 難しいのは「分割箇所と区間の交点数」を最大化するのではなく、 「分割される区間数」を最大化したいので、 ある区間が複数の分割箇所を含んでいても、数えられるのは1回である点である。

    3   1   4   1   5   9 | 2   6   5 | 3   5   8   9   7   9
3  (  *   *   *   *   *   +   *   *   +  )
1      (  *   *  )        |           |
5                  (  *   +   *   *  )|
5                         |        (  +   *  )
9                      (  +   *   *   +   *   *   *  )
9                         |           |            (  *   *  )

計6個の交点があるが、3, 9 は1つの区間が2回区切られているため、
「分割される区間数」としては4個。

右側の分割箇所を左から右へ全探索する。

左側の分割箇所は、「右側の分割箇所が完全に過ぎ去った区間」だけで重なり数の最大値を取りたい。
これは、各区間の始点・終点位置を前計算しておけば、区間add操作・区間max取得の遅延セグメント木で可能である。

Python3

G - Maximize Distance

問題文

  • N 頂点 M 辺の有向グラフが与えられます。
    • 頂点には 1,2,,N の番号がついており、辺 j (j=1,2,,M) は頂点 uj から頂点 vj に向かいます。
    • 頂点 1 から頂点 N に到達可能であることが保証されます。
  • はじめ、すべての辺の重みは 0 です。
  • M 本の辺からちょうど K 本の辺を選んで重みを 1 に変更するとき、変更後のグラフにおける頂点 1 から頂点 N への最短距離としてあり得る最大値を求めてください。

制約

  • 2N30
  • 1KM100
  • 1uj,vjN
  • ujvj
  • 与えられるグラフにおいて、頂点 1 から頂点 N へ到達可能である
  • 入力はすべて整数

誤った解法

最短距離を 1 にするのに必要な本数は「最小カット」と一致するので、 これが K 以下になるかどうかで「少なくとも答えが 1 以上になるかどうか」はわかる。

なんとなくこの繰り返し、つまり「最小カットを解き、カットされた辺をINFにする」を、 必要なカット辺の累積本数が K を超えるまで繰り返せばよいのでは?という解法が思いつく。

だが、これは最小カットの候補が複数あるとき、どれを選ぶかによって2回目以降の結果が異なる場合があり、WAとなる。

N=4 M=6 K=5
             最適解は、③→②以外の5辺を採用した、最短距離 2。
①⇉②→④   しかし、1回目で「①→③ と ②→④」の組がカット辺として成立する。
  ↘↑↗     それを選んだ場合、2回目は全ての辺をカットせねばならず、
    ③       K を超えるため、最短距離 1 と判定されてしまう。
             ※1回目に「②→④ と ③→④」をカット辺とすると成功する。

ジャッジサーバに用意されたテストケースでは、 この解法で1ケース以外通ってしまったんだよな。

解法

二分探索 + k値燃やす埋める。頂点の意味の持たせ方が難しい。

「最短距離を d にするために必要な本数は K 以下か?」を判定する。
この判定問題が解けるなら、d を二分探索すればよい。

最小カットで解く。
最小カットとは、「頂点集合を S,T の2グループに分類する方法のうち、始点は S、終点は T である全ての方法の中で、ST の間に張られた辺の重み合計が最小となるような分類、またその時の重み合計値」なので、それで判定問題が解けるように上手く辺を張る。

d×N 頂点用意して、長方形に並べる。

d=3 N=4

(1,1)  (1,2)  (1,3)  (1,4)

(2,1)  (2,2)  (2,3)  (2,4)

(3,1)  (3,2)  (3,3)  (3,4)

頂点 (k,v) は、グループ S,T のそれぞれに分類されたとき、以下を意味するものとする。

  • S に分類されたとき、頂点 1 から v までの最短距離は k 未満
  • T に分類されたとき、頂点 1 から v までの最短距離は k 以上

縦に並ぶ頂点間には、以下のように辺を張る。

  • k=1,2,...,d1 について、(k,v)(k+1,v) の辺は、
    • (k,v)S(k+1,v)T に分類された場合、頂点 v の最短距離が「k 未満」かつ「k+1 以上」ということになってしまう。
    • あり得ないため、カットを禁じるように の辺を張る。
    • 言い換えれば、(1,v),(2,v),...,(d,v) の分類は必ず T,...,T,S,...S となるようにする。S または T の連続数が0のこともある。
      • 問題に答える上では特に個別に求める必要は無いが、最後に T に分類された頂点を (k,v) とすると、k が「最小カットに従ってコストを1にする辺を設定した状態における頂点 v の最短距離」となる

また元のグラフに存在した辺を uv として、

  • (k,u)(k,v)
    • これが ST となる場合、uk 未満かつ vk 以上となることを表す。uv にコスト1の辺を張る。
  • (k,u)(k+1,v)
    • これが ST となる場合、uk 未満かつ vk+1 以上となるが、1本の辺によって直接結ばれた頂点間の距離が2以上異なることはあり得ない。 の辺を張る。

これで (1,1) から (d,N) まで流せば、最小カットが「最短距離を d にするときの必要な本数」となる。

Python3

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