Processing math: 7%

サントリープログラミングコンテスト2023(AtCoder Beginner Contest 321)F,G問題メモ

F - #(subset sum = K) with Add and Erase

問題

  • 正整数の多重集合 S に、以下の2種類の操作を合計 Q 回、指定された順番に行う
    • Sx を1つ追加する
    • S から x を1つ削除する
  • 各操作後、以下の問題に \mod{998244353} で答えよ
    • S の要素を好きに選んで、和をちょうど K にする方法の個数
  • 1 \le Q \le 5000
  • 1 \le K \le 5000
  • 1 \le x \le 5000

解法

減らす場合のナップサック。

問題を見るに、1回だけなら典型的なナップサック問題。

  • DP[i,j]=i 番目の要素まで見て、和が j=0~K となる方法の個数

でも1回あたり O(QK) なので、毎回ゼロから答えを求めると、O(Q^2K) となり間に合わない。
通常のナップサックは荷物を入れる順番は関係ないので、計算済みの DP から「減らす」操作をしたい。

DP[i,j] に要素 x を追加すると DP[i+1,j+x] に遷移するのだから、
j の上限を K で切らず、全ての場合を持っておけば、DP[i,j+x] から DP[i+1,j] のように減らす場合も計算できるのでは?
だがそれは要素数が O(Qx_{max}) のレベルで増えていくので無理。

ところが、よく考えると、実は j の上限は K のままで、減らす場合も計算できる。

       0  1 ...  x  ... j ... j+x ... K

i      a  b ...  c  d . k ...  e  ... z     通常のナップサックの更新:
                 a  b ........ k  ...         xずらして加算
      ----------------------------------
i+1    a  b ... a+c b+d ..... e+k ...

通常のナップサックでの x の追加は、DP[i] 同士を、x ずらして足し込むイメージ。

逆に x を減らす更新を行うとき、「直前で追加したのが x だったとして、x を追加する前の状態」を想像する。

すると、j の添字の小さい方から順に、DP[i,j+x] から DP[i,j] を引いてやると、元の状態が復元できることが分かる。
この操作に、j \gt K の情報は必要ない。

Python3

G - Electric Circuit

問題

  • N 個の電子基板がある
  • 赤の端子は電子基板 R_1,R_2,...,R_M に、青の端子は B_1,B_2,...,B_M についている(重複あり)
  • M 本のケーブルで、赤と青の端子を1つずつ M 組のペアにしてつなぐ
    • 同じ基板に付いた2つをつないでもよい
  • つなぎ方は M! 通りあるが、ランダムに選ぶときの連結成分数の期待値を \mod{998244353} で求めよ
  • 1 \le N \le 17
  • 1 \le M \le 10^5

解法

主客転倒とbitDP

期待値は、M! 通りのつなぎ方全ての連結成分数の総和を求めた上で M! で割ればよいので、総和を求めることを目指す。

また、これは主客転倒して、「ある基板の集合 S に対し、それが1つの連結成分を為すようなケーブルのつなぎ方の個数」 を求めて、その総和を取ることと一致する。

S の中の赤(青)端子」を k 個、「S の中だけのケーブルのつなぎ方のうち、S が1つの連結成分をなすもの」を x 通りとすると、S の外の端子はどうつなごうと自由なので、S が1つの連結成分を為すつなぎ方は x(N-k)! で計算できる。

で、肝心の「S の中だけのケーブルのつなぎ方のうち、S が1つの連結成分をなすもの」は、 「S の中だけのケーブルの全てのつなぎ方 k!」から、「2つ以上の連結成分に分かれるようなつなぎ方」を除外するとよい。

  • DP[S]=S の中で1つの連結成分を為す、S の中だけのケーブルのつなぎ方

DPの遷移

いま、1つ S を決めて DP[S] を求めるとする。S の部分集合のDPの値は、S 自身を除いて全て計算済みとする。

赤と青の端子の個数が一致していなければそもそも不可能なので DP[S]=0。以下、一致しているとする。

赤端子の個数を k とすると、まず全体では k! 通りのつなぎ方がある。

そのうち、2つ以上の連結成分に分かれるものを除去するにあたり、 「最も番号が小さい基板が属する連結成分 T」を、S の部分集合(S 自身以外)から総当たりする。
最も番号が小さい基板に限定するのは、重複を防ぐため。

T の端子数を l として、DP[T] \times (k-l)! 通りのつなぎ方が除去される。

k! から全てを除去した後に残ったのが、DP[S] となる。

計算量は O(3^N)

Python3

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