Processing math: 100%

NECプログラミングコンテスト2021(AtCoder Beginner Contest 229) F,G,H問題メモ

F - Make Bipartite

問題

  • N+1 頂点の無向グラフ
  • 辺は、以下の 2N
    • i=1,2,...,N について、頂点 0i を結ぶ重み Ai の辺
    • i=1,2,...,N について、頂点 ii+1 を結ぶ重み Bi の辺
      • ただし頂点 N+11 を表すとする
  • 辺を削除して全体を二部グラフにしたい
  • 削除する必要のある最小の辺の重みの総和を求めよ
  • 3N2×105

解法

グラフに見せかけたDP。

グラフは車輪のような形になる。

  ①--②
 / \  / \
⑥--⓪--③
 \ /  \ /
  ⑤--④

二部グラフは、頂点を2グループに分け「同じグループの間には辺が張られていない」状態ならよい。

グループAとBに分け、頂点0の入る方をグループAと決めてしまうとして、 頂点 0i1 で成立している状態に i を加えることを考える。

  • i をAに入れるなら、Ai は削除しないといけない
  • ii1 と同じグループに入れるなら、Bi1 は削除しないといけない
  • その他の辺は削除しなくてよい

ただし、i=1 のとき、2番目の条件は「1N が同じグループなら BN は削除」となる。
このループ部分(BN の辺)がややこしいので、頂点1に関しては2番目の条件は一端無視する。

頂点 i を加えるときの条件から、i1 の入ったグループがどっちだったかさえわかれば、削除しなければならない辺が決まる。

  • (仮)DP[i][g]= 頂点 1i を考え、i の入ったグループが g=A/B の時の、削除すべき辺のコストの最小値

また無視していた BN については、DP[N] まで計算した後で 「頂点 1N をそれぞれどちらに入れたか」がわかれば、削除しなければならないかが決まる。

従って、そこでも場合分けしてやればよい。

  • DP[i][f][g]= 頂点 1i を考え、頂点 1f=A/B に入れ、i の入ったグループが g=A/B の時の、削除すべき辺のコストの最小値

最後に、DP[N][A][A] および DP[N][B][B] には BN を加算してやれば、全ての辺を考慮できたことになる。

DP[N] の中の4つの値の最小値が答え。

Python3

G - Longest Y

問題

  • '.' と 'Y' からなる文字列 S が与えられる
  • K 回まで、隣接する文字をswapできる
  • 'Y' をなるべく長く連続させたいとき、可能な最大長を求めよ
  • 2|S|2×105

解法

いくつかの気付きと前準備と、添字をバグらせない丁寧さが必要。

答えを二分探索できないか考える。
Yを x 個連続させられるかどうかを O(|S|) くらいで判定できれば可能そう。

x を固定して考える。

Y同士を入れ替える操作は無駄なので、S の中のYの並び順は最初から最後まで変わらないとしてよい。
S の中で i 番目に出現するYのことを Yi と表すとする。

連続させる一番左のYを Yi としたら、一番右は Yi+x1 に決まる。

この区間のYを連続させるためのswap回数を求めたい。

基準となるYを1個決める。 基準のYは動かさず、周囲のYを持ってくることを考える
Y..Y.YY....[Y].YY

基準Yから左右それぞれのYとの間にある'.'の個数を数えたら、その和がswap回数
Y..Y.YY....[Y].YY
7  5 44        11  →  7+5+4+4+1+1 = 22

ここで、たとえば基準を1個左にずらした時の変化量は、その間に'.'が k 個あったとして

  • 左にあるYの個数 ×k だけ減る
  • 右にあるYの個数 ×k だけ増える

なので、最適な基準位置は、区間中央のYであることがわかる。偶数の場合はどちらでもよい。

Y..Y.Y[Y]....Y.YY
3  1 0       4 55  →  3+1+0+4+5+5 = 18

従って、以下が前計算によりパッと求められると嬉しい。

  • R(i,j)=Yi を基準として右側 Yi+1Yj のYを Yi に持ってくる回数
  • L(i,j)=Yi を基準として左側 YjYi1 のYを Yi に持ってくる回数

愚直には各 Yi から各 Yj の間にある'.'の個数を数えた上で累積和を取っておけばよいのだが、 しかし2次元でテーブルを作ってしまうと O(|S|2) のためTLE or MLE。

そこで、基準を1個ずらしたときに法則性が無いかを確かめると、

      Y..Y.YY....Y.YY
基準     / | \   | | \
Y0   0  2  5  8 15 23 31
    (  -2 -4 -6 -8-10-12)
Y1      0  1  2  7 13 19
    (     -1 -2 -3 -4 -5)
Y2         0  0  4  9 14

Y3            0  4  9 14
    (           -4 -8-12)
Y4               0  1  2
...

ずらすごとに等差数列を引いた形になっている。
まぁ、ずらしたところから右にある各Yまでにある'.'の個数が一定数ずつ減るので、累積和を取ったら等差で減るのは当然っちゃ当然。

基準を Yi にする時に追加される等差数列の公差は、直前の Yi1 との間にある'.'の数であり、R(i,i) が0になるように位置を揃える。

  • R(0,:)
  • 基準を i にするときの、R(0,:) からの累積公差
  • 基準を i にするときの、R(0,:) からの累積初項
    • 初項=このまま等差数列を左端まで延長した場合の Y0 の箇所の値

以上の長さ O(|S|) の3つの配列を前計算しておけば、差分計算で R(i,j)O(1) で求められる。

L(i,j) も同様にすればよい。

すると区間 [l,l+x) のYを連続させるswap回数は、m を区間中点、r=l+x1 として、 L(m,l)+R(m,r) で求められ、これが K 以下なら可能となる。

x ごとに l を全通り試しても O(|S|) で判定でき、全体で O(|S|log|S|) となる。

Python3

解法2

上記のような七面倒くさいことしなくても、ちょっと発想を変えればもっと楽に計算できる。

公式Editorialの解法。二分探索は一緒。

S からYの箇所のindexを抜き出した配列を A とすると、1回のswapは、どれか1個を±1する操作に対応する。
(Y同士のswapは行わない前提として、異なる Ai が同じ値になる瞬間があってはいけない)

Y..Y.YY....Y.YY    A = (0  3  5  6 11 13 14)
      ×                        ↓+1
Y..Y.Y.Y...Y.YY    A = (0  3  5  7 11 13 14)

Yを連続させるというのは、A に連番をなるべく長く発生させるという問題に言い換えられる。

さらにこれは、あらかじめ Ai から i を引いて Bi とおくことで、「同じ値を」なるべく多く作る問題にできる。

Y....YYYY....YY    A = (0  5  6  7  8 13 14)
                   B = (0  4  4  4  4  8  8)

すると、[l,r) の区間のYを揃えるには、中央 m に揃えるのが最適となるので、

              ■■                 ↓↓      
Bm        ■■■■   →  ↑↑↑■■■■
        ■■■■■       ↑↑■■■■■
    ■■■■■■■       ■■■■■■■
    l     m       r      l     m       r
  • Bm(ml)(BlBm1)+(BmBr1)Bm(rm)

累積和を前計算しておけば操作回数を O(1) で計算できる。

H - Advance or Eat

問題文

  • NN 列のグリッドがあり、盤面の初期状態が与えられる
    • 各マスには白い駒が 1 個置かれているか、黒い駒が 1 個置かれているか、何も置かれていないかのいずれか
  • 高橋君とすぬけ君がゲームをする。高橋君からはじめて、交互にターンが回ってくる
  • 高橋君のターンには、以下のいずれかの操作ができる
    • 1 つ上に空マスがあるような白い駒を 1 つ選び、上に進める
    • 好きな黒い駒を 1 つ食べる
  • すぬけ君のターンには、以下のいずれかの操作ができる
    • 1 つ上に空マスがあるような黒い駒を 1 つ選び、上に進める(※高橋君とすぬけ君の「上」は同じ方向を指す)
    • 好きな白い駒を 1 つ食べる
  • 操作ができなくなった方が負け。どちらが必勝?

制約

  • 1N8

解法

非不偏ゲームの解析。知ってないと自力で思いつくのは厳しい。公式Editorialを読んでの個人的なメモ。

盤面最大 64 マスにつき「白、黒、何も無い」の3通りの状態を持つ 364 のDPは、 初期盤面から遷移可能な状態に絞ったところで、さすがに状態数が多すぎて無理。

Nimのように「どちらのプレイヤーも同じ手を打てる」ゲーム(不偏ゲーム)の場合は、 Grundy数などを使って効率的に解けることがあるが、今回はそうではない。

もし不偏ゲームなら、今回の問題では「各列は互いに干渉しない」ので、1列1列をNimの山のように個別に考えられそうなのに。。。

ところが、非不偏ゲームでも一定の条件を満たす場合は、Grundy数ほど簡単ではないが、1列ずつを個別に考える方法がある。

  • 条件
    • 二人零和有限確定完全情報ゲームである
    • 後述の「状態に対するスコア」を、上手く設定できる

超現実数

スコアには超現実数(Surreal Number)というのを用いる。

分母が2冪に限定された実数であり、特に「2つの数に挟まれた数」を特定の方法で一意に決められるのが特徴。

{L|R} という表記は、以下の条件を満たす実数 x を示す。この条件から一意に決まる。

  • L<x<R
  • そのような x の中で、既約分数として表したとき、分母が2冪の中で最も小さい
  • 分母が1の場合は可能な候補が複数生まれうるが、絶対値が 0 に最も近いものとする
    • 上記2条件は、「超現実数を二分木表現したとき、最も根に近い数」といえる(二分木表現は2番目のリンク参照)

ゲーム木の構築とスコアの設定

今回の場合、ゲーム木の1頂点は「1列」の状態とする。
頂点から頂点へ、先手が可能な遷移は「白い辺」、後手が可能な遷移は「黒い辺」を張るとする。

各頂点に「スコア(評価値)」を割り当てるのだが、ここに超現実数を用いる。
以下の条件でスコア付けすることを考える。

  • 白い辺にしたがって遷移したらスコアは必ず減少する
  • 黒い辺にしたがって遷移したらスコアは必ず増加する

そのためには、葉からスコアを決定する上で、以下ならよい。

  • ある頂点からの「全ての白い辺の遷移先のスコア最大値 SW」<「全ての黒い辺の遷移先のスコア最小値 SB

先ほどの超現実数を用いて、{SW|SB} で表される実数を、その頂点のスコアとする。
(遷移できる白い辺が無い場合は、SW=、黒い辺が無い場合は SB= などとしておく)

全ての頂点でこの条件を満たすようにスコア付けすることができればよい。仮に、今はできたと仮定する。

c の現在の状態を vc とし、そのスコアを Svc で表す。
現在の盤面の全ての列のスコア合計 cSvcT とする。
T の正負は、以下のように推移する。

  • 先手の手番で T が正の時
    • 先手操作後の T も必ず正または0が保たれる(理由は後述)
    • 後手は、T が正か0の状態で回ってくると、操作後は必ず正になる
    • 評価値が正の状態で、先手が操作できなくなることはない → 先手必勝
  • 先手の手番で T が0または負の時
    • 先ほどの逆で、先手の操作後は必ず負になる
    • 後手は、負で回ってくると、操作後は必ず負か0になる
    • 評価値が負の状態で、後手が操作できなくなることはない → 後手必勝

よって、「初期状態の T が正なら先手必勝、0か負なら後手必勝」となる。

(今回の問題では必要ないが)より細かく言うと、
  • T が正なら、「白い辺を使う方」の必勝
  • T が負なら、「黒い辺を使う方」の必勝
  • T が0なら、「後手」の必勝

となり、「白黒辺」による勝利と、「先手後手」による勝利の2種類がある。

T が正なら先手の操作後も必ず非負となる理由

たとえば「T=0.125=18」の時に、 「どの列の、どの白い辺の遷移も、Sv18 より大きく減少してしまう」状態になったら、 0を飛び越して一気に負にならざるをえない。

逆に、「T を既約分数にした時の分母が 2d(上例では8)なら、白い辺の遷移による減少は、必ず 12d 以下である」ことが示せればよい。

で、これは必ず言える。

  • T の分母が 8 なら、個々の Sv の中に、必ず分母が 8 以上(16,32等)のものがある
  • Sv{SW|SB} のように、遷移先の白最大値と黒最小値に挟まれる値で決まっている
  • たとえば Sv=38 だとして、そこからの白い辺での遷移の減少幅が 18 より大きいなら、SW<28=14 である
  • しかし、それなら「分母が小さくなるように」Sv は決まるので、Sv=14 となっていないとおかしい
T が正なら先手は必ず操作できる理由

T が正なら、当然、正である Sv が1つは存在する。

白い辺が出ていない、Sv>0 の頂点を考える。

Sv={SW|SB} という定義を考えたとき、白い辺が無いなら SW= である。
また、Sv が正なら SB も正ということになる。

しかし、それなら超現実数のルール上、Sv=0 となっているはずであり、矛盾する。

Sv が正なら、白い辺は必ず出ていることになる。

全ての頂点でスコアの条件を満たせる証明

どのようなゲームでもスコアを上手く設定できるわけではなく、 今回のゲーム設定がたまたま性質を満たしているので効率的に解ける、ということになる。

いま、状態 v のスコア Sv を決めるとして、そこから遷移できる全ての状態は既にスコアが矛盾なく求まっているとする。

v からの遷移先で、どのように白い辺 va と黒い辺 vb のペアを持ってきても、 遷移先のスコア Sa<Sb であることが示せればよい。

操作には「ある駒を進める」「ある駒を食べる」の種類があるが、

  • 先手は va に相当する操作を、「後手が vb の操作を行った後」でもできる
  • 後手は vb に相当する操作を、「先手が va の操作を行った後」でもできる

この2つがともに成り立つことを、「互いに干渉しない」とする。

互いに干渉しない場合、ゲーム木は以下のようになっており、

 ,--> a ==,     -->: 白い辺
v          w    ==>: 黒い辺
 `==> b --'

Sa<Sw<Sb なので、問題なく Sv を設定できる。

「互いに干渉する」のは、「先に行動する方が駒を先に食べると、後に行動する方はその駒を動かせなくなる」のみである。

その場合、例えば先手が先に行動するなら

vの状態  aの状態  bの状態
  ・       ・       ●        ・: 空きマス
  ●       ・       ・        ●: 黒い駒

 ,--> a     -->: 白い辺
v     ↑    ==>: 黒い辺
 `==> b

このように、状態 b からでも先手は黒を食べて a に遷移することができ、ba にも白い辺が張られていることになる。

よって、Sa<Sb が成り立ち、問題なく Sv を設定できる。

Python3

programming_algorithm/contest_history/atcoder/2021/1127_abc229.txt · 最終更新: 2024/05/25 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0