結果

問題 No.3321 岩井星人グラフ-1
コンテスト
ユーザー Neculapia
提出日時 2025-11-25 21:06:51
言語 C++23
(gcc 13.3.0 + boost 1.89.0)
結果
WA  
実行時間 -
コード長 16,267 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 6,479 ms
コンパイル使用メモリ 153,104 KB
実行使用メモリ 37,372 KB
最終ジャッジ日時 2025-11-25 21:07:45
合計ジャッジ時間 11,217 ms
ジャッジサーバーID
(参考情報)
judge2 / judge5
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 3
other AC * 68 WA * 21
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

/**
 * 解法アプローチ:
 * 1. 入力グラフは N 頂点 M 辺で、1辺追加して岩井星人グラフ(N頂点 N辺、次数条件、距離条件)を作る必要がある。
 * 2. 最終的に N 辺になるため、入力は M = N - 1(森)であることが必要条件。
 * 3. 岩井星人グラフの条件:
 *    - 次数1の頂点数 = 次数3の頂点数 = X
 *    - 全ての次数1頂点から、最も近い次数3頂点への距離が Y-1 (L) である。
 * 4. 辺追加による次数変化と X の整合性を確認するため、Gap = Count(Deg1) - Count(Deg3) に着目する。
 *    - Gap = 2 の場合 (通常の木構造など):
 *      - Mode 11: 葉と葉を結ぶ (X = C3)。
 *      - Mode 12: 葉と次数2を結ぶ (X = C3 + 1)。
 *      - Mode 22: 次数2同士を結ぶ (X = C3 + 2)。
 *    - Gap = 0 かつ 孤立点ありの場合:
 *      - Mode 01, 02 を試す。
 * 5. 各モードについて、距離条件 L を満たす候補ペアを探索する。
 *    - 距離条件を満たさない「悪い葉」を特定し、それらを被覆できる候補頂点を集合交差や探索で絞り込む。
 *    - N が最大 3x10^5 なので、候補全探索は不可。必要条件(距離 L、葉に近づきすぎない等)で候補を絞り、
 *      最終確認 (check_final) で実際にBFSを行い判定する。
 */

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <set>
#include <string>

using namespace std;

// 定数・グローバル変数
const int INF = 1e9;
int N, M;
vector<vector<int>> adj;
vector<int> deg;

// 距離計算用 (BFS)
void get_dists(const vector<int>& sources, vector<int>& d) {
    fill(d.begin(), d.end(), INF);
    queue<int> q;
    for (int u : sources) {
        d[u] = 0;
        q.push(u);
    }
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int v : adj[u]) {
            if (d[v] == INF) {
                d[v] = d[u] + 1;
                q.push(v);
            }
        }
    }
}

// 2点間距離 (BFS)
int get_single_dist(int s, int t) {
    if (s == t) return 0;
    queue<pair<int, int>> q;
    q.push({s, 0});
    vector<int> visited_dist(N + 1, -1);
    visited_dist[s] = 0;
    
    while (!q.empty()) {
        auto [u, d] = q.front(); q.pop();
        if (u == t) return d;
        if (d >= visited_dist[s] + N) continue; // Safety break, though BFS finds shortest
        
        for (int v : adj[u]) {
            if (visited_dist[v] == -1) {
                visited_dist[v] = d + 1;
                q.push({v, d + 1});
            }
        }
    }
    return INF;
}

// 最終チェック: 辺 (u, v) を追加して条件を満たすか
bool check_final(int u, int v, int target_X, int target_L) {
    // 辺追加
    deg[u]++; deg[v]++;
    adj[u].push_back(v);
    adj[v].push_back(u);
    
    // 次数3以上の頂点を始点にBFS
    vector<int> sources;
    for (int i = 1; i <= N; ++i) {
        if (deg[i] == 3) sources.push_back(i);
        else if (deg[i] > 3) { // 次数超過チェック
            deg[u]--; deg[v]--; adj[u].pop_back(); adj[v].pop_back();
            return false; 
        }
    }
    
    // Xの一致確認
    int n1 = 0;
    for (int i = 1; i <= N; ++i) if (deg[i] == 1) n1++;
    if (n1 != target_X || (int)sources.size() != target_X) {
        deg[u]--; deg[v]--; adj[u].pop_back(); adj[v].pop_back();
        return false;
    }
    
    vector<int> d(N + 1, -1);
    queue<int> q;
    for (int s : sources) {
        d[s] = 0;
        q.push(s);
    }
    
    bool valid = true;
    while (!q.empty()) {
        int curr = q.front(); q.pop();
        // Lより深い探索は判定に不要だが、L未満の葉を見つけるために必要
        // ただし最適化として L+1 程度で打ち切る手もあるが、正確性重視
        if (d[curr] > target_L) continue; 
        
        for (int nxt : adj[curr]) {
            if (d[nxt] == -1) {
                d[nxt] = d[curr] + 1;
                q.push(nxt);
            }
        }
    }
    
    for (int i = 1; i <= N; ++i) {
        if (deg[i] == 1) {
            if (d[i] != target_L) {
                valid = false;
                break;
            }
        }
    }
    
    // 戻す
    deg[u]--; deg[v]--;
    adj[u].pop_back(); adj[v].pop_back();
    return valid;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    
    if (!(cin >> N >> M)) return 0;
    
    adj.resize(N + 1);
    deg.resize(N + 1, 0);
    
    for (int i = 0; i < M; ++i) {
        int u, v;
        cin >> u >> v;
        adj[u].push_back(v);
        adj[v].push_back(u);
        deg[u]++; deg[v]++;
    }
    
    // 次数超過チェック
    for (int d : deg) if (d > 3) { cout << "No" << endl; return 0; }
    
    int n1 = 0, n3 = 0, n0 = 0;
    for (int i = 1; i <= N; ++i) {
        if (deg[i] == 1) n1++;
        else if (deg[i] == 3) n3++;
        else if (deg[i] == 0) n0++;
    }
    
    int gap = n1 - n3;
    vector<pair<string, int>> candidates;
    
    if (n0 == 0) {
        if (gap == 2) {
            candidates.push_back({"11", n3});
            candidates.push_back({"12", n3 + 1});
            candidates.push_back({"22", n3 + 2});
        }
    } else if (n0 == 1) {
        if (gap == 0) {
            candidates.push_back({"01", n3});
            candidates.push_back({"02", n3 + 1});
        }
    }
    
    if (candidates.empty()) { cout << "No" << endl; return 0; }
    
    // 前計算: 既存の次数3からの距離
    vector<int> dist_old(N + 1);
    vector<int> sources;
    for (int i = 1; i <= N; ++i) if (deg[i] == 3) sources.push_back(i);
    get_dists(sources, dist_old);
    
    // 前計算: 最寄りの葉までの距離
    vector<int> leaves;
    for (int i = 1; i <= N; ++i) if (deg[i] == 1) leaves.push_back(i);
    vector<int> dist_to_leaf(N + 1);
    get_dists(leaves, dist_to_leaf);
    
    for (auto [mode, X] : candidates) {
        if (X <= 0 || N % X != 0) continue;
        int Y = N / X;
        if (Y < 2) continue;
        int L = Y - 1;
        
        if (mode == "11") {
            vector<int> bad;
            for (int l : leaves) if (dist_old[l] != L) bad.push_back(l);
            
            if (bad.size() == 2) {
                if (check_final(bad[0], bad[1], X, L)) { cout << "Yes" << endl; return 0; }
            } else if (bad.empty()) {
                if (leaves.size() >= 2) {
                    if (check_final(leaves[0], leaves[1], X, L)) { cout << "Yes" << endl; return 0; }
                }
            }
        }
        else if (mode == "22") {
            // 条件: dist_old >= L であること(近すぎる葉は救えない)
            bool possible = true;
            for (int l : leaves) if (dist_old[l] < L) { possible = false; break; }
            if (!possible) continue;
            
            vector<int> bad_leaves;
            for (int l : leaves) if (dist_old[l] != L) bad_leaves.push_back(l);
            
            // 悪い葉をカバーできる候補頂点集合を計算
            vector<set<int>> cover_sets;
            for (int l : bad_leaves) {
                set<int> s;
                // BFSで距離Lの次数2頂点を探す
                queue<pair<int, int>> q;
                q.push({l, 0});
                set<int> vis; vis.insert(l);
                while (!q.empty()) {
                    auto [u, d] = q.front(); q.pop();
                    if (d == L) {
                        if (deg[u] == 2) s.insert(u);
                        continue; 
                    }
                    if (d < L) {
                        for (int v : adj[u]) {
                            if (vis.find(v) == vis.end()) {
                                vis.insert(v);
                                q.push({v, d + 1});
                            }
                        }
                    }
                }
                if (s.empty()) { possible = false; break; }
                cover_sets.push_back(s);
            }
            if (!possible) continue;
            
            // 有効な次数2頂点 (他の葉に近すぎない)
            vector<int> valid_deg2;
            for (int i = 1; i <= N; ++i) {
                if (deg[i] == 2 && dist_to_leaf[i] >= L) valid_deg2.push_back(i);
            }
            set<int> valid_set(valid_deg2.begin(), valid_deg2.end());
            
            // cover_sets を valid_set でフィルタリング
            vector<set<int>> filtered_sets;
            for (const auto& s : cover_sets) {
                set<int> inter;
                for (int x : s) if (valid_set.count(x)) inter.insert(x);
                if (inter.empty()) { possible = false; break; }
                filtered_sets.push_back(inter);
            }
            if (!possible) continue;
            
            if (filtered_sets.empty()) {
                if (valid_deg2.size() >= 2) {
                    if (check_final(valid_deg2[0], valid_deg2[1], X, L)) { cout << "Yes" << endl; return 0; }
                }
            } else {
                sort(filtered_sets.begin(), filtered_sets.end(), [](const set<int>& a, const set<int>& b){
                    return a.size() < b.size();
                });
                
                // Set Coverの探索 (u を最小集合から選ぶ)
                int limit = 0;
                for (int u : filtered_sets[0]) {
                    if (++limit > 50) break;
                    
                    vector<int> uncovered_idx;
                    for (size_t i = 0; i < filtered_sets.size(); ++i) {
                        if (filtered_sets[i].find(u) == filtered_sets[i].end()) uncovered_idx.push_back(i);
                    }
                    
                    if (uncovered_idx.empty()) {
                        for (int v : valid_deg2) {
                            if (v != u) {
                                if (check_final(u, v, X, L)) { cout << "Yes" << endl; return 0; }
                                break; 
                            }
                        }
                    } else {
                        set<int> cand_v = filtered_sets[uncovered_idx[0]];
                        for (size_t k = 1; k < uncovered_idx.size(); ++k) {
                            set<int> next_s;
                            set_intersection(cand_v.begin(), cand_v.end(),
                                             filtered_sets[uncovered_idx[k]].begin(), filtered_sets[uncovered_idx[k]].end(),
                                             inserter(next_s, next_s.begin()));
                            cand_v = next_s;
                            if (cand_v.empty()) break;
                        }
                        int v_limit = 0;
                        for (int v : cand_v) {
                            if (++v_limit > 50) break;
                            if (v != u) {
                                if (check_final(u, v, X, L)) { cout << "Yes" << endl; return 0; }
                            }
                        }
                    }
                }
            }
        }
        else if (mode == "12") {
            // 条件: dist_old < L である葉が2つ以上あったら無理
            vector<int> too_close;
            for (int l : leaves) if (dist_old[l] < L) too_close.push_back(l);
            if (too_close.size() > 1) continue;
            
            vector<int> potential_us;
            if (too_close.size() == 1) {
                potential_us.push_back(too_close[0]);
            } else {
                // 距離不整合の葉を優先
                for (int l : leaves) if (dist_old[l] > L || dist_old[l] == INF) potential_us.push_back(l);
                if (potential_us.size() > 50) potential_us.resize(50);
                
                // 距離整合の葉も少し試す
                int cnt = 0;
                for (int l : leaves) {
                    if (dist_old[l] == L) {
                        potential_us.push_back(l);
                        if (++cnt >= 10) break;
                    }
                }
            }
            
            for (int u : potential_us) {
                // uを葉から除外した状況で、v (deg 2) が他のすべての葉に対して適切か確認
                // needed: v によってカバーされるべき葉 (dist_old > L or INF)
                vector<int> needed;
                for (int l : leaves) if (l != u && (dist_old[l] > L || dist_old[l] == INF)) needed.push_back(l);
                
                if (needed.empty()) {
                    // v は他の全ての葉から距離 L 以上離れていれば良い
                    int v_tries = 0;
                    for (int v = 1; v <= N; ++v) {
                        if (deg[v] == 2) {
                            // dist_to_leaf[v] は u も含む距離なので、u が近い分には許容される
                            // dist_to_leaf[v] >= L なら安全
                            // dist_to_leaf[v] < L の場合、その近さが u によるものならOK
                            if (dist_to_leaf[v] >= L) {
                                if (check_final(u, v, X, L)) { cout << "Yes" << endl; return 0; }
                                if (++v_tries > 50) break;
                            } else {
                                // 近い葉が u だけか確認
                                int d_uv = get_single_dist(u, v);
                                if (d_uv == dist_to_leaf[v]) {
                                    // 可能性あり
                                    if (check_final(u, v, X, L)) { cout << "Yes" << endl; return 0; }
                                    if (++v_tries > 50) break;
                                }
                            }
                        }
                    }
                } else {
                    // needed の共通部分を探す
                    int l0 = needed[0];
                    set<int> cands;
                    // BFS from l0
                    queue<pair<int,int>> q; q.push({l0, 0});
                    set<int> vis; vis.insert(l0);
                    while(!q.empty()){
                        auto [c, d] = q.front(); q.pop();
                        if(d == L){
                            if(deg[c] == 2) cands.insert(c);
                            continue;
                        }
                        if(d < L){
                            for(int nxt : adj[c]){
                                if(vis.find(nxt) == vis.end()){
                                    vis.insert(nxt);
                                    q.push({nxt, d+1});
                                }
                            }
                        }
                    }
                    
                    int v_tries = 0;
                    for(int v : cands) {
                        if(v == u) continue;
                        if(check_final(u, v, X, L)) { cout << "Yes" << endl; return 0; }
                        if(++v_tries > 50) break;
                    }
                }
            }
        }
        else if (mode == "01") {
            int iso = -1;
            for(int i=1; i<=N; ++i) if(deg[i]==0) { iso = i; break; }
            if(iso == -1) continue;
            
            for(int l : leaves) {
                if(dist_old[l] == L - 1) {
                    if(check_final(iso, l, X, L)) { cout << "Yes" << endl; return 0; }
                }
            }
        }
        else if (mode == "02") {
            int iso = -1;
            for(int i=1; i<=N; ++i) if(deg[i]==0) { iso = i; break; }
            if(iso == -1) continue;
            
            bool ok = true;
            for(int l : leaves) if(dist_old[l] != 1) { ok = false; break; }
            if(ok) {
                int tries = 0;
                for(int v=1; v<=N; ++v) {
                    if(deg[v]==2) {
                        if(check_final(iso, v, X, L)) { cout << "Yes" << endl; return 0; }
                        if(++tries > 10) break;
                    }
                }
            }
        }
    }
    
    cout << "No" << endl;
    return 0;
}
0