2025 XCPC浙江省競技プログラミングコンテスト FLM問題解説

F. Challenge NPC III

多起点最短経路と第二最短経路問題。

同じ色の頂点に対してBFSを実行し、各経路の起点を維持します。同じ色の頂点から自身への経路が最短であるため、最終的に第二最短経路がkより小さいかを判定すれば十分です。

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {
    int n, m, k;
    cin >> n >> m >> k;

    vector<vector<int>> color(50);
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        color[--x].push_back(i);
    }

    vector<vector<int>> graph(n);
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        u--, v--;
        graph[u].push_back(v);
    }

    const int inf = 1 << 30;

    for (int i = 0; i < 50; i++) {
        if (color[i].size() <= 1) continue;
        
        vector<vector<int>> dist(n, {inf, inf});
        vector<vector<int>> prev(n, {-1, -1});
        
        queue<int> q;
        for (int u : color[i]) {
            dist[u] = {0, inf};
            prev[u] = {u, -1};
            q.push(u);
        }
        
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            
            for (int v : graph[u]) {
                bool updated = false;
                for (int a : {0, 1}) {
                    for (int b : {0, 1}) {
                        if (prev[u][a] == prev[v][b] || prev[u][a] < 0) continue;
                        if (dist[u][a] + 1 < dist[v][b] && prev[v][b ^ 1] != prev[u][a]) {
                            dist[v][b] = dist[u][a] + 1;
                            prev[v][b] = prev[u][a];
                            updated = true;
                        }
                    }
                }
                if (updated) {
                    q.push(v);
                }
            }
        }
        
        for (int u : color[i]) {
            if (dist[u][1] < k) {
                cout << "NO\n";
                return;
            }
        }
    }
    
    cout << "YES\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

L. Nailoongs Always Lie

環状木構造における動的計画法問題。

n個の頂点とn個の辺からなるグラフは必ず環を形成するため、これは環状木の森となります。環上の一辺を切断すると、木構造上で2回の動的計画法を実行でき、複数の連結成分における最大値の和を求めます。

頂点uがNailoongである場合、頂点vは必ずNailoongではありません。頂点uがNailoongでない場合、頂点vはNailoongである場合とそうでない場合の両方が考えられます。
dp[u,0/1]をuがNailoongであるかどうかを表す状態として、遷移方程式は以下のようになります:

[dp_{u,1}=\sum_{v \in Sub_u}dp_{v,0} ][dp_{u,0}=\sum_{v \in Sub_u}\max(dp_{v,0},dp_{v,1}) ]辺を切断する際のdp値の処理に注意が必要です。

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n;
    
    vector<vector<int>> forward_graph(n + 1);
    vector<vector<int>> reverse_graph(n + 1);
    
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        forward_graph[i].push_back(x);
        reverse_graph[x].push_back(i);
    }
    
    int mark = 0;
    vector<int> visited(n + 1);
    vector<vector<int>> dp(n + 1);
    
    auto calculate = [&](auto &&self, int u) -> int {
        visited[u] = 1;
        dp[u] = {0, 1};
        
        for (int v : reverse_graph[u]) {
            if (v == mark) {
                dp[v][1] = -1E9;
                continue;
            }
            self(self, v);
            dp[u][1] += dp[v][0];
            dp[u][0] += max(dp[v][1], dp[v][0]);
        }
        
        return max(dp[u][0], dp[u][1]);
    };
    
    auto find_cycle = [&](auto &&self, int u) -> void {
        visited[u] = 1;
        if (forward_graph[u].empty()) return;
        
        if (visited[forward_graph[u][0]]) {
            mark = u;
        } else {
            self(self, forward_graph[u][0]);
        }
    };
    
    int result = 0;
    for (int i = 1; i <= n; i++) {
        if (visited[i]) continue;
        
        find_cycle(find_cycle, i);
        if (mark == 0) continue;
        
        int temp = calculate(calculate, mark);
        mark = forward_graph[mark][0];
        temp = max(temp, calculate(calculate, mark));
        result += temp;
    }
    
    cout << result << "\n";
    
    return 0;
}

M. Master of Both VII

思考問題。

すべての3 ≤ i ≤ n-1に対する(1,i)のクエリを考え、結果をd_iとします。d_i = 0の場合は直接答えに追加します。

辺(x,y)は、x < i < yのクエリに対して1の貢献をします。

これらの辺は、非端点で交差しない区間を形成するため、2 ≤ i ≤ nの順序でスタックを管理します。

  • もしd_i > d_{i-1}ならば、スタックにd_i - d_{i-1}個のi-1をプッシュします。
  • もしd_i < d_{i-1}ならば、d_{i-1} - d_i個のスタックトップをポップし、辺(st[top], i)を形成します。

i-1で始まる区間とiで終わる区間が同時に存在しないことが証明できるため、このアプローチの正しさが保証されます。

時間計算量はO(n)です。

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {
    int n;
    cin >> n;
    
    vector<pair<int, int>> edges;
    
    auto query = [](int l, int r) -> int {
        cout << "? " << l << " " << r << endl;
        int res;
        cin >> res;
        return res;
    };
    
    auto output = [&]() -> void {
        cout << "! ";
        for (auto &[x, y] : edges) {
            cout << x << " " << y << " ";
        }
        cout << endl;
    };
    
    stack<int> st;
    int last = 0;
    
    for (int i = 3; i <= n; i++) {
        int x = 0;
        if (i < n) {
            x = query(1, i);
        }
        
        if (x == 0 && i < n) {
            edges.push_back({1, i});
            while (!st.empty()) {
                edges.push_back({st.top(), i});
                st.pop();
            }
            last = x;
            continue;
        }
        
        if (x > last) {
            for (int j = 0; j < x - last; j++) {
                st.push(i - 1);
            }
        } else if (x < last) {
            for (int j = 0; j < last - x; j++) {
                edges.push_back({st.top(), i});
                st.pop();
            }
        }
        
        last = x;
    }
    
    output();
    
    cin >> n;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    
    return 0;
}

タグ: BFS 動的計画法 木構造 最短経路 環状木

8月1日 18:35 投稿