初めて TechFUL に参加してみたんですが、1 位でスイッチ lite がもらえることになったみたいで嬉し~なのいみです。
割と簡単(青 diff くらい?)めの問題が多かった印象ですが、この「羽根つき」の問題だけ少し難しめに感じました。解説の計算量は らしいですが、
で解けたので簡単に解法を紹介しようと思います。
問題
戦先取の羽根つきを A, B の二人で行う。
対
になったら B の勝ち、というルールが
個提案されている。
A が勝つような試合の通り数が奇数になるようなルールの選び方を
で割った余りを求めよ。
問題リンク
解法
斜めに bit dp をしながら見ていきます。
とします。
から
を追加ルールを一切追加しないものとして求めた後に追加ルールを適用することを考えます。
この場合の DP は簡単に求まります(A が勝つか負けるかで場合分け)。 ただし、A が N 勝している、というケースは場合分けする必要があることに注意してください。
追加ルールについて処理をします。
についてルールを追加することは、
のルールに対応する勝敗の組み合わせが奇数ならば偶数にすること (bit が立っていれば 0 にすること) に対応します。
において、ルールに対応する勝敗の組み合わせが奇数の(bit が立っている)個数を
個、偶数の個数を
個とします。
ルール
通りをそれぞれ選んで加算することは、
通りの部分集合について
をそれぞれ加算することに対応します。
- 例えば、6 戦目に関して、
というルールが存在するとします。
という集合に対して 8 通りのルールを加算することは、
という 4 通りの集合に 2 ずつ加算することに対応します。というのも、
のように、元々対応する勝敗になる組み合わせが偶数の場合はルールを追加するかどうかは影響しないので、単に 2 を掛ければいいことがわかります。
のように、対応する勝敗が奇数の場合はルールを選ぶことでその組み合わせを偶数 (0) に変えることができます。これは各 bit について独立に行えるので、ありうるすべての部分集合が列挙されます。
また、これは各 について先に
を掛け算した後に、ルールが存在するような bit についてのみ高速ゼータ変換を適用することで高速に求まります。
for(int a = 0; a < n; a++) {
if(rule[a][i - a] exists) {
for(b in B) {
if(b & 1 << a) dp[b ^ 1 << a] += dp[b];
}
}
}
ただし、B はありうる b の集合です。
正方形のグリッドを斜めに見ているので、考える必要のある長さは となっているため、全体での計算量は
となります。