F - Shiritori Editorial by MMNMM
「これまでに使っていない単語の集合」と「最後の文字」のペアを状態に持った DP を行うことでこの問題を解くことができます。
- \(\operatorname{dp}[S][c]\coloneqq\) これまでに使っていない単語の集合が \(S\subseteq\{s _ 1,\ldots,s _ N\}\) であり、最後の文字が \(c\) である状態について、その状態から勝てるとき \(1\) 、そうでないとき \(0\)
であるような DP テーブルを考えます。 このとき、この DP テーブルは次のようにして値を決めることができます。
\[\operatorname{dp}[S][c]=\left\{\!\!\begin{array}{cll}1&&({}^\exists s\in S.\;s[0]=c\wedge\operatorname{dp}[S\setminus\{s\}][s[-1]]=0)\\0&&(\text{otherwise})\end{array}\right. \]
ただし、\(s[0],s[-1]\) はそれぞれ \(s\) の最初の文字、最後の文字を表します。 この式は、「残っている単語のうち、それを言ったら相手を負かせるものがあるとき、かつそのときに限り勝つ」ことを DP の遷移に書き下したものです。
\(\operatorname{dp}[\{s_1,\ldots,s_N\}][c]\) が \(1\) であるような \(c\) が存在すれば高橋くんは勝つことができます。
この DP テーブルを正しく埋めるためには、\(\operatorname{dp}[S][c]\) を計算しようとするより先に \(\operatorname{dp}[S\setminus\{s\}][s[-1]]\) を計算していなければなりません。 この条件はいわゆる bit DP で用いる方法で \(S\) を \(N\operatorname{bit}\) の整数に変換して昇順に計算を行えば満たされます。
文字の種類数を \(\sigma\) として、状態が \(2^N\sigma\) 通りあり、状態 \(1\) つあたり \(O(N)\) 時間かけて DP テーブルの計算を行うので全体で時間計算量は \(O(2^NN\sigma)\) となります。
実装例は以下のようになります。 ここではワードサイズ \(w\) に対して \(\sigma\lt w\) が成り立っていることを仮定した実装をしています。
#include <iostream>
#include <vector>
#include <utility>
#include <string>
int main() {
using namespace std;
unsigned N;
cin >> N;
// i 番目の文字列の最初の文字/最後の文字
vector<pair<unsigned char, unsigned char>> info(N);
for (auto&&[first, last] : info) {
string S;
cin >> S;
first = S.front() - 'a';
last = S.back() - 'a';
}
// dp[S] の c bit 目 = S が残っていて直前の文字が c のときの勝敗
vector<unsigned> dp(1UL << N);
for (unsigned S{1}; S < 1UL << N; ++S)
for (unsigned c{}; c < N; ++c)
if (1 & S >> c)
dp[S] |= (1UL & ~dp[S ^ 1UL << c] >> info[c].second) << info[c].first;
// dp[2^N - 1] に立っているビットがあれば先手の勝ち
cout << (dp.back() ? "First" : "Second") << endl;
return 0;
}
posted:
last update: