問題
チョコレートの板が 4 行に並んでいる。1 行目(一番下)の長さが $a$、2 行目が $b$、3 行目が $c$、4 行目が $d$ で、$a\ge b\ge c\ge d\ge0$、$a\ge1$。1 行目の左端の 1 片は毒である。
2 人が交互に次の手を指す。
- 行 $r\in\{1,2,3,4\}$ と、その行の今の長さより短い $x\ge0$ を選び、行 $r$ とそれより上のすべての行の長さを $\min(\text{今の長さ},x)$ にする(選んだ片とその右上をすべて食べる)
- ただし 1 行目で $x=0$(毒を食べる)は選べない
手が無い(板が毒 1 片 $(1,0,0,0)$ だけ)プレイヤーの負けである。
$Q$ 個の局面 $(a,b,c,d)$ について、両者が最善を尽くしたとき先手が勝つなら First、後手が勝つなら Second を出力せよ。
入力形式
Q
a_1 b_1 c_1 d_1
...
a_Q b_Q c_Q d_Q
制約
| # | 制約 |
|---|---|
| 1 | $1\le Q\le2\times10^5$ |
| 2 | $0\le d_i\le c_i\le b_i\le200$ |
| 3 | $\max(b_i,1)\le a_i\le10^{18}$ |
入出力例
入力例1
6
4 1 1 1
3 1 1 1
3 1 1 0
5 2 1 1
8 3 3 3
1000000000000000000 4 1 1出力例1
Second
First
Second
Second
Second
First(2 つ目と 3 つ目が要点。3 行の Chomp で後手勝ちの $(3,1,1)$ に 4 行目を 1 片足すと先手勝ちになり、後手勝ちの 1 行目は 4 に移る。最後の $(b,c,d)=(4,1,1)$ には後手勝ちの $a$ が存在しない(1 行目を 3 に縮めた $(3,3,1,1)$ が後手勝ちなので、どんな $a$ でもそこへ動ける)。6 局面とも全状態のミニマックス探索と一致)
背景と出典
- Chomp は Gale(1974)が名付け、Schuh(1952)が約数のゲームとして考えた、と私は記憶している(原典は未確認)。長方形の板では戦略盗み論法で先手必勝だが、勝ち手を作る一般の方法は知られていない
- 3 行の Chomp は Zeilberger “Three-Rowed CHOMP”(2001 と記憶、未確認)がコンピュータで調べた。4 行以上を扱った文献があるかは本セッションで調べていない
- 本問の問題文・制約・入力例は自作。性質「$(b,c,d)$ ごとに後手勝ちの $a$ は高々 1 つ」と表の作り方は本セッションで導き、全探索で確かめた
概念図
ヒント(クリックで段階的に開示)
ヒント1(方向性)
$(b,c,d)$ を固定する。後手勝ち(P 局面)の $a$ が 2 つ $a<a'$ あったら、$(a',b,c,d)$ から 1 行目を $a$ に縮める手($a\ge b$ なので上の行は変わらない)で P へ動けてしまう。だから P の $a$ は高々 1 つ。これを $P(b,c,d)$ と書く(無ければ「無し」)。
ヒント2(アプローチ)
$(a,b,c,d)$ から 1 行目以外を縮める手は 3 種:
- 2 行目を $y<b$ に: $(a,\ y,\ \min(c,y),\ \min(d,y))$。P なのは $a=P(y,\min(c,y),\min(d,y))$ のとき
- 3 行目を $z<c$ に: $(a,b,z,\min(d,z))$。P なのは $a=P(b,z,\min(d,z))$
- 4 行目を $w<d$ に: $(a,b,c,w)$。P なのは $a=P(b,c,w)$
1 行目を $x\in[b,a)$ に縮める手は「より小さい $a$ の P」へ行く手。1 行目を $x<b$ に縮める手は $a$ によらない行き先 $(x,x,\min(c,x),\min(d,x))$。
ヒント3(誘導)
- 1 行目を $x$($1\le x<b$)に縮めた局面が P になる $x$ があれば、$P(b,c,d)=$ 無し(どの $a$ でもそこへ勝ちに行ける)。それは $P(x,\min(c,x),\min(d,x))=x$ のこと
- 無しでなければ、$P(b,c,d)$ は「3 種の集合のどれにも無い、$\max(b,1)$ 以上の最小の数」= mex
- 集合を整数のビットで持つ。$b$ の昇順、$c$ の昇順、$d$ の昇順に埋めると、3 つの集合はどれも計算済みの値だけでできる
模範解答 (Python)
模範解答を表示する(まず自力で解いてから)
import sys
def build(n):
"""P[b][c][d]: (a,b,c,d) が後手勝ちになる唯一の a(無ければ -1)。n >= b >= c >= d >= 0"""
NONE = -1
P = [[[NONE] * (c + 1) for c in range(b + 1)] for b in range(n + 1)]
# rowmask[c][d]: b'<b の P(b', min(c,b'), min(d,b'))(2 行目を縮める手の行き先)
rowmask = [[0] * (n + 1) for _ in range(n + 1)]
# dead[c][d]: 1 行目を x (1<=x<b) に縮めた (x,x,min(c,x),min(d,x)) が後手勝ちになる x があるか
dead = [[False] * (n + 1) for _ in range(n + 1)]
for b in range(n + 1):
lo = b if b >= 1 else 1
Pb = P[b]
colmask = [0] * (n + 1) # z<c の P(b, z, min(d,z))(3 行目を縮める手)
for c in range(b + 1):
Pbc = Pb[c]
rm = rowmask[c]
dd = dead[c]
cm = colmask
wmask = 0 # w<d の P(b, c, w)(4 行目を縮める手)
for d in range(c + 1):
if dd[d]:
v = NONE
else:
x = (rm[d] | cm[d] | wmask) >> lo
v = lo + (~x & (x + 1)).bit_length() - 1
wmask |= 1 << v
Pbc[d] = v
for d in range(n + 1): # 後の c' > c から見た「3 行目を c に縮める手」
v = Pbc[d if d < c else c]
if v >= 0:
colmask[d] |= 1 << v
for c in range(n + 1): # 後の b' > b から見た「2 行目を b に縮める手」
cc = c if c < b else b
Pbc = Pb[cc]
rm = rowmask[c]
dd = dead[c]
for d in range(c + 1):
v = Pbc[d if d < cc else cc]
if v >= 0:
rm[d] |= 1 << v
if b >= 1 and v == b: # (b,b,..) が後手勝ち → 以後この (c,d) は 1 行目を b に縮めて勝てる
dd[d] = True
return P
def main():
data = sys.stdin.buffer.read().split()
q = int(data[0])
qs = [tuple(int(v) for v in data[1 + 4 * i:5 + 4 * i]) for i in range(q)]
n = max(b for _, b, _, _ in qs)
P = build(n)
print("\n".join("Second" if P[b][c][d] == a else "First" for a, b, c, d in qs))
if __name__ == "__main__":
main()
計算量: 表は $(b,c,d)$ の $O(N^3/6)$ 個、各 mex は長さ $O(N)$ ビットの整数演算なので全体 $O(N^4/w)$。$Q=2\times10^5$ 込みで $N=100$: 0.23 s・86 MB、$N=200$: 0.79 s・113 MB、$N=300$: 2.71 s・210 MB、$N=400$: 6.65 s・401 MB(Python 3.14.8、Apple M5 Pro、負荷平均 20〜38 での実測、起動込み)。これを見て制約を $b\le200$ とした。
Step-by-Step 解説
11 行目だけが特別
1 行目を $x\ge b$ に縮めても上の行は変わらない。だから $(b,c,d)$ を固定した「1 行目の長さ $a$」の列は、Nim の 1 山のように「より小さい $a$ へ動ける」。後手勝ちは高々 1 つ(ヒント 1)。
2行き先の 3 つの集合
2・3・4 行目を縮める手の行き先は、どれも 1 行目が $a$ のまま。行き先が P になる $a$ の集合を
- $R(c,d)=\{P(y,\min(c,y),\min(d,y)) : y<b\}$
- $C(d)=\{P(b,z,\min(d,z)) : z<c\}$
- $W=\{P(b,c,w) : w<d\}$
とする。$a$ がこれらに入っていれば、その手で後手勝ちへ動けるので先手勝ち。
3mex と「無し」
$a\ge\max(b,1)$ で、3 つの集合に入らず、かつそれより小さい P の $a$ も無い(それは自動的に満たす。最小を取るので)なら P。ただし 1 行目を $x<b$ に縮めた局面 $(x,x,\cdot,\cdot)$ が P なら、どの $a$ でも勝てるので無し。その判定は $P(x,\min(c,x),\min(d,x))=x$。
4埋める順序
$b$ の昇順に進めれば $R$ は「前の $b$ までの値」、$c$ の昇順に進めれば $C$ は「同じ $b$ の前の $c$ の値」、$d$ の昇順に進めれば $W$ は「同じ $(b,c)$ の前の $d$ の値」だけでできる。3 行の Day177-Q1 の rowmask・colmask に、4 行目用の wmask が 1 つ加わった形。
5mex をビット演算で
集合を整数のビット列で持ち、x = mask >> lo の最下位の 0 ビットを (~x & (x+1)).bit_length()-1 で取る。Day177-Q1 で set 版と比べて 25 倍速かった方法をそのまま使う。
迷った他案と採らなかった理由
| 案 | 計算量 | 採否 | 理由 |
|---|---|---|---|
| 全局面のミニマックス | 局面 $O(AN^3)$ | 検算用のみ | $a\le10^{18}$ では不可能 |
| 3 行の表を使い 4 行目を無視 | $O(N^3/w)$ | 不採用(誤り) | $b\le12,a\le50$ の 19109 局面中 544 で誤る(実測) |
| 「無し」の判定を省く | $O(N^4/w)$ | 不採用(誤り) | 676/19109 誤り(実測) |
| 4 行目を縮める手の集合 $W$ を省く | 同上 | 不採用(誤り) | 544/19109 誤り(実測) |
| 3 行目を縮める手の集合 $C$ を省く | 同上 | 不採用(誤り) | 543/19109 誤り(実測) |
| Grundy 数の表を全局面で作る | $O(N^4)$ 局面 × $a$ | 不採用 | 勝敗だけなら P の $a$ 1 つで足りる |
| $(b,c,d)$ ごとの P を mex で(本解) | $O(N^4/w)$ | 採用 | $N=200$ で 0.79 s |
前提が変わったら
- $k$ 行に一般化すると、表は $O(N^{k-1})$ 個、行き先の集合は $k-1$ 種になる。5 行なら $N=60$ 程度が Python の目安($N^4/24\approx5\times10^5$ 個)と見積もるが、測っていない
- $b$ が $10^3$ 以上なら $O(N^3)$ 個の表がメモリに載らない($N=400$ で 401 MB)。$a$ の上限が無い問いに答えるには表が要るので、局面ごとに必要な $(b,c,d)$ だけを遅延で作る工夫が要る(未実装)
- 勝ち手を 1 つ出せなら、どの集合($R,C,W$)に $a$ が入ったかを覚えておけば復元できる
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 2 行目を $y$ に縮めたとき 3・4 行目を $\min$ しない | 「その行だけ短くなる」と読む | 上の行すべてを $\min(\cdot,y)$ |
| mex の下限を $b$ にし忘れる | $a\ge b$ の制約を忘れる | $\max(b,1)$ 以上の最小 |
| $b=0$ のとき下限を 0 にする | 毒 1 片 $(1,0,0,0)$ の扱い | $a\ge1$ なので下限 1 |
| 「無し」を $x\in[0,b)$ で判定 | 1 行目 $x=0$ は毒で選べない | $1\le x<b$ |
| colmask を $b$ ごとに作り直さない | 3 行目を縮める手は同じ $b$ の中だけ | $b$ ごとに 0 から |
次のステップ
- 発展1: 5 行の表を作り、「無し」の割合が 3 行 41%・4 行 44% からどう変わるか測る
- 発展2: $P(b,c,d)$ の最大値が $N=200$ で 459、$N=400$ で 914(どちらも約 $2.3N$)だった。3 行は $N=2000$ で 3416(約 $1.7N$)。行数と係数の関係を調べる
- 発展3: 長方形の板($a=b=c=d$)の先手の勝ち手を表から求め、戦略盗み論法の「存在」を具体的な手にする
検証ログ(本セッションで実行した数値)
- 正しさ(実測): $b\le12$、$\max(b,1)\le a\le50$ の全 19109 局面で、全状態のミニマックス探索と表の判定が全件一致($b\le12$ の $P$ の最大は 28 なので、$a\le50$ ですべての P を含む)。$b\le9,a\le30$ の 5334 局面でも一致
- 誤った方法(実測、同じ 19109 局面): 3 行の表 544、「無し」を省く 676、$W$ を省く 544、$C$ を省く 543 局面で誤る
- 表の性質(実測): $N=50$ で 23426 個中 10308 個(44.0%)、$N=100$ で 44.8%、$N=200$ で 1373701 個中 607792 個(44.2%)、$N=400$ で 43.3% が「無し」。$P$ の最大は $N=50$: 115、$N=100$: 229、$N=200$: 459、$N=400$: 914
- 具体例(実測): $P(1,1,1)=4$、$P(2,1,1)=5$、$P(1,1,0)=3$(3 行の $(3,1,1)$)、$P(3,3,3)=8$、$P(4,1,1)=$ 無し
- 時間(実測、起動込み、$Q=2\times10^5$、負荷平均 20〜38): $N=100$ 0.23 s、$N=200$ 0.79 s・113 MB、$N=300$ 2.71 s・210 MB、$N=400$ 6.65 s・401 MB
- 扱っていない範囲: 4 行以上の文献、5 行以上、勝ち手の復元