結果
| 問題 |
No.3340 Simple Graph Game
|
| コンテスト | |
| ユーザー |
|
| 提出日時 | 2025-11-11 09:24:20 |
| 言語 | C++23 (gcc 13.3.0 + boost 1.87.0) |
| 結果 |
WA
|
| 実行時間 | - |
| コード長 | 3,884 bytes |
| コンパイル時間 | 1,415 ms |
| コンパイル使用メモリ | 128,864 KB |
| 実行使用メモリ | 36,624 KB |
| 最終ジャッジ日時 | 2025-11-13 21:17:41 |
| 合計ジャッジ時間 | 7,066 ms |
|
ジャッジサーバーID (参考情報) |
judge2 / judge4 |
(要ログイン)
| ファイルパターン | 結果 |
|---|---|
| sample | AC * 3 |
| other | AC * 53 WA * 5 |
ソースコード
// Gemini 2.5 Pro に嘘ヒントを教えて出力された嘘解法
#include <iostream>
#include <vector>
#include <string>
#include <set> // 縮約グラフの重複辺除去
#include <atcoder/scc> // ACLのSCCライブラリ
using namespace std;
using namespace atcoder;
// 勝敗の状態 (0: LOSE, 1: WIN, 2: DRAW)
enum GameResult { LOSE = 0, WIN = 1, DRAW = 2 };
int main() {
int N, M;
cin >> N >> M;
scc_graph graph(N);
vector<pair<int, int>> edges; // 元の辺リスト (0-indexed)
vector<bool> has_self_loop(N, false); // 自己ループ判定用
for (int i = 0; i < M; ++i) {
int u, v;
cin >> u >> v;
u--; // 0-indexed に
v--;
graph.add_edge(u, v);
edges.push_back({u, v});
if (u == v) {
has_self_loop[u] = true;
}
}
// --- 1. SCC ---
vector<vector<int>> sccs = graph.scc();
int num_scc = sccs.size();
// scc_id[v] = 頂点vが属するSCCのID
vector<int> scc_id(N);
for (int i = 0; i < num_scc; ++i) {
for (int v : sccs[i]) {
scc_id[v] = i;
}
}
// --- 2. サイクル判定 (DRAWの可能性) ---
// scc_is_draw[i] = SCC i がサイクル(DRAW)可能か
vector<bool> scc_is_draw(num_scc, false);
for (int i = 0; i < num_scc; ++i) {
if (sccs[i].size() >= 2) {
// 頂点数が2以上ならサイクル
scc_is_draw[i] = true;
} else {
// 頂点数が1の場合、自己ループがあるか
int u = sccs[i][0];
if (has_self_loop[u]) {
scc_is_draw[i] = true;
}
}
}
// --- 3. 縮約グラフ構築 ---
// scc_adj[i] = SCC i から到達可能なSCCのIDリスト (重複なし)
vector<set<int>> scc_adj(num_scc);
for (const auto& edge : edges) {
int u_id = scc_id[edge.first];
int v_id = scc_id[edge.second];
if (u_id != v_id) {
scc_adj[u_id].insert(v_id);
}
}
// --- 4. 後退解析 (DP) ---
// dp[i] = SCC i からゲームを始めたときの勝敗
vector<GameResult> dp(num_scc);
// ACLのSCCはトポロジカルソート順なので、IDの大きい方(末端)から処理
for (int i = num_scc - 1; i >= 0; --i) {
// デフォルトは LOSE (遷移先がすべて WIN か、遷移先なし)
dp[i] = LOSE;
bool can_move_to_draw = false;
// 遷移先をチェック
for (int next_scc_id : scc_adj[i]) {
if (dp[next_scc_id] == LOSE) {
// 優先度1: 相手をLOSEにできるなら、自分はWIN
dp[i] = WIN;
break; // このSCCの判定終了
}
if (dp[next_scc_id] == DRAW) {
can_move_to_draw = true;
}
}
// (もし dp[i] == WIN なら、この continue が実行される)
if (dp[i] == WIN) continue;
// (LOSEには行けなかった場合)
if (scc_is_draw[i]) {
// 優先度2: 自分がサイクル持ち (DRAW可能)
dp[i] = DRAW;
} else if (can_move_to_draw) {
// 優先度3: 自分がサイクル持ちでないが、DRAW には行ける
dp[i] = DRAW;
}
// 優先度4: (dp[i] == LOSE のまま)
// LOSEに行けず、自分もサイクルでなく、DRAWにも行けない
// = 遷移先はすべてWIN、または遷移先なし
}
// --- 5. 判定 ---
int start_scc_id = scc_id[0]; // スタート(頂点1 -> 0)
if (dp[start_scc_id] == WIN) {
cout << "Alice" << endl;
} else if (dp[start_scc_id] == LOSE) {
cout << "Bob" << endl;
} else { // DRAW
cout << "Draw" << endl;
}
return 0;
}