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

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

問題

  • 正整数の多重集合 $S$ に、以下の2種類の操作を合計 $Q$ 回、指定された順番に行う
    • $S$ に $x$ を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