結果
問題 |
No.1770 N言っちゃダメゲーム (6)
|
ユーザー |
![]() |
提出日時 | 2025-05-14 13:20:24 |
言語 | C++17 (gcc 13.3.0 + boost 1.87.0) |
結果 |
TLE
|
実行時間 | - |
コード長 | 5,134 bytes |
コンパイル時間 | 737 ms |
コンパイル使用メモリ | 77,256 KB |
実行使用メモリ | 6,272 KB |
最終ジャッジ日時 | 2025-05-14 13:21:43 |
合計ジャッジ時間 | 5,634 ms |
ジャッジサーバーID (参考情報) |
judge1 / judge5 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 3 |
other | AC * 25 TLE * 1 -- * 18 |
ソースコード
#include <iostream> #include <vector> #include <algorithm> #include <numeric> // num_good_options[s]: プレイヤーが合計 s を受け取った際、 // 次に相手を負けさせることができる自分の「良い手」の数。 // first_good_option[s]: num_good_options[s] == 1 の場合、その唯一の「良い手」となる整数の値。 std::vector<int> num_good_options; std::vector<int> first_good_option; int N_val, K_val; // プレイヤーが状態 (current_sum, prev_move_by_opponent) を受け取った場合に負けるかどうかを判定する関数。 // prev_move_by_opponent は、相手が current_sum を作るために加算した数。 bool does_player_lose(int current_sum, int prev_move_by_opponent) { // current_sum >= N_val の場合は既に前のプレイヤーがN以上を宣言して負けている。 // この関数は current_sum < N_val の状況で呼ばれることを想定。 int N_opts = num_good_options[current_sum]; int F_opt = first_good_option[current_sum]; if (N_opts == 0) { return true; // プレイヤーには有効な「良い手」がないため負ける。 } if (N_opts == 1 && F_opt == prev_move_by_opponent) { // プレイヤーには「良い手」が一つだけあるが、それが相手が直前に使った手なので禁止されている。よって負ける。 return true; } // 上記以外の場合、プレイヤーは有効な「良い手」を選べるので勝つ(負けない)。 return false; } int main() { std::ios_base::sync_with_stdio(false); std::cin.tie(NULL); std::cin >> N_val >> K_val; num_good_options.resize(N_val); first_good_option.resize(N_val); // s を N_val - 1 から 0 まで降順に計算 // s: 現在のプレイヤーが受け取った合計値 for (int s = N_val - 1; s >= 0; --s) { int current_s_num_good_options = 0; int current_s_first_good_option = 0; // 現在のプレイヤーが取りうる手 k_try (1 から K_val まで) を試す for (int k_try = 1; k_try <= K_val; ++k_try) { if (s + k_try >= N_val) { // この手 k_try を選ぶと、合計が N_val 以上になり、現在のプレイヤーが負ける。 // これは「良い手」ではない。 // k_try は昇順なので、これ以上大きな k_try も同様。ループを打ち切る。 break; } // 現在のプレイヤーが k_try を選んだ場合、相手は合計 (s + k_try) を受け取り、 // その合計を作った手は k_try となる。 // 現在のプレイヤーがこの手 k_try で勝つのは、相手が状態 (s + k_try, k_try) で負ける場合。 if (does_player_lose(s + k_try, k_try)) { current_s_num_good_options++; if (current_s_num_good_options == 1) { current_s_first_good_option = k_try; } if (current_s_num_good_options == 2) { // 「良い手」が2つ見つかれば十分。 // (相手がどちらか一方の手を禁止しても、もう一方が使えるため) break; } } } num_good_options[s] = current_s_num_good_options; // num_good_options[s]が0の場合、first_good_option[s]は初期値0のまま。 // num_good_options[s]が2以上の場合、first_good_option[s]は最初に見つかった「良い手」。 // does_player_lose関数内では、num_good_optionsが1の場合のみfirst_good_optionの値が重要になるので、これで問題ない。 if (current_s_num_good_options > 0) { // current_s_num_good_optionsが0の時に初期値0以外が入らないように first_good_option[s] = current_s_first_good_option; } else { first_good_option[s] = 0; // 念のため } } std::vector<int> winning_first_moves; // あなた(最初のプレイヤー)は合計0からスタート。相手の「直前の手」は存在しないので0(ダミー値)とする。 // あなたは x (1 から K_val) を宣言する。相手は状態 (x, x) を受け取る。 // あなたが勝つのは、相手が状態 (x, x) で負けるような x が存在する場合。 for (int x = 1; x <= K_val; ++x) { if (x >= N_val) { // あなたが宣言する数 x が N_val 以上なら、あなた自身の負け。 break; } if (does_player_lose(x, x)) { winning_first_moves.push_back(x); } } if (winning_first_moves.empty()) { std::cout << 0 << "\n"; } else { // winning_first_moves は自動的に昇順になる (x を1から昇順に試しているため) for (size_t i = 0; i < winning_first_moves.size(); ++i) { std::cout << winning_first_moves[i] << "\n"; } } return 0; }