結果

問題 No.3589 Make Ends Meet (Hard)
コンテスト
ユーザー marc2825
提出日時 2026-05-30 09:44:43
言語 C++17
(gcc 15.2.0 + boost 1.90.0)
コンパイル:
g++-15 -O2 -lm -std=c++17 -Wuninitialized -DONLINE_JUDGE -o a.out _filename_
実行:
./a.out
結果
AC  
実行時間 967 ms / 2,000 ms
コード長 6,973 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 1,705 ms
コンパイル使用メモリ 242,840 KB
実行使用メモリ 8,448 KB
最終ジャッジ日時 2026-07-10 20:58:53
合計ジャッジ時間 5,482 ms
ジャッジサーバーID
(参考情報)
judge3_0 / judge2_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 3
other AC * 47
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

#include <bits/stdc++.h>
using namespace std;

using ll = long long;

static const int MOD = 998244353;

int addmod(int a, int b) {
    a += b;
    if (a >= MOD) a -= MOD;
    return a;
}

int submod(int a, int b) {
    a -= b;
    if (a < 0) a += MOD;
    return a;
}

int mulmod(ll a, ll b) {
    return int(a * b % MOD);
}

int brute(int N, int M, int K) {
    vector<pair<int,int>> edges;
    for (int i = 0; i < N; i++) {
        for (int j = i + 1; j < N; j++) {
            edges.push_back({i, j});
        }
    }

    int E = edges.size();
    int R = E - M;

    if (E > 62) return -1;

    unsigned long long LIM = 1ULL << E;
    int ans = 0;

    for (unsigned long long mask = 0; mask < LIM; mask++) {
        // mask = 残す辺集合
        if (__builtin_popcountll(mask) != R) continue;

        vector<vector<int>> G(N);
        for (int e = 0; e < E; e++) {
            if ((mask >> e) & 1ULL) {
                auto [u, v] = edges[e];
                G[u].push_back(v);
                G[v].push_back(u);
            }
        }

        vector<int> dist(N, -1);
        queue<int> q;
        dist[0] = 0;
        q.push(0);

        while (!q.empty()) {
            int v = q.front();
            q.pop();

            for (int to : G[v]) {
                if (dist[to] == -1) {
                    dist[to] = dist[v] + 1;
                    q.push(to);
                }
            }
        }

        if (dist[N - 1] == K) {
            ans++;
            if (ans >= MOD) ans -= MOD;
        }
    }

    return ans;
}

vector<int> multiply_poly_terms(const vector<int>& poly, const vector<pair<int,int>>& terms, int R) {
    vector<int> res(R + 1, 0);

    for (int i = 0; i <= R; i++) {
        if (poly[i] == 0) continue;

        for (auto [d, b] : terms) {
            if (i + d > R) break;
            res[i + d] = (res[i + d] + (ll)poly[i] * b) % MOD;
        }
    }

    return res;
}

int solve_fast(int N, int M, int K) {
    int E = N * (N - 1) / 2;
    int R = E - M;

    if (R < 0) return 0;

    // comb[n][r], n <= E, r <= R
    vector<vector<int>> comb(E + 1, vector<int>(R + 1, 0));
    for (int i = 0; i <= E; i++) {
        comb[i][0] = 1;
        for (int j = 1; j <= min(i, R); j++) {
            if (j == i) comb[i][j] = 1;
            else comb[i][j] = addmod(comb[i - 1][j - 1], comb[i - 1][j]);
        }
    }

    // 頂点選択用 C[n][r]
    vector<vector<int>> C(N + 1, vector<int>(N + 1, 0));
    for (int i = 0; i <= N; i++) {
        C[i][0] = C[i][i] = 1;
        for (int j = 1; j < i; j++) {
            C[i][j] = addmod(C[i - 1][j - 1], C[i - 1][j]);
        }
    }

    auto one_plus_x_pow = [&](int t) {
        vector<pair<int,int>> terms;
        for (int i = 0; i <= min(t, R); i++) {
            if (comb[t][i]) terms.push_back({i, comb[t][i]});
        }
        return terms;
    };

    // trans[p][q]:
    // 直前層サイズ p、現在層サイズ q の遷移多項式
    //
    // 現在層内部の辺は自由
    // 現在層の各頂点は直前層へ少なくとも 1 本辺を持つ
    vector<vector<vector<pair<int,int>>>> trans(
        N + 1,
        vector<vector<pair<int,int>>>(N + 1)
    );

    for (int p = 1; p <= N; p++) {
        for (int q = 1; q <= N; q++) {
            if (p + q > N) continue;

            int inside = q * (q - 1) / 2;
            vector<int> arr(R + 1, 0);

            // (1+x)^inside * ((1+x)^p - 1)^q
            // = sum_{r=0}^{q} (-1)^{q-r} C(q,r) (1+x)^{inside + p*r}
            for (int r = 0; r <= q; r++) {
                int coef = C[q][r];
                bool plus = ((q - r) % 2 == 0);
                int total = inside + p * r;

                for (int d = 0; d <= min(total, R); d++) {
                    int val = mulmod(coef, comb[total][d]);
                    if (plus) arr[d] = addmod(arr[d], val);
                    else arr[d] = submod(arr[d], val);
                }
            }

            for (int i = 0; i <= R; i++) {
                if (arr[i]) trans[p][q].push_back({i, arr[i]});
            }
        }
    }

    int mid = N - 2;

    // key = used * (N+1) + last_size
    // used: 中間頂点 2..N-1 のうち、すでに距離層に使った数
    // last_size: 直前の距離層サイズ
    map<pair<int,int>, vector<int>> dp;
    dp[{0, 1}] = vector<int>(R + 1, 0);
    dp[{0, 1}][0] = 1;

    // L1, L2, ..., L_{K-1}
    for (int layer = 1; layer <= K - 1; layer++) {
        map<pair<int,int>, vector<int>> ndp;

        for (auto& it : dp) {
            auto [used, prev_size] = it.first;
            const vector<int>& poly = it.second;

            int remain = mid - used;

            for (int cur_size = 1; cur_size <= remain; cur_size++) {
                if (trans[prev_size][cur_size].empty()) continue;

                int ways = C[remain][cur_size];

                vector<int> npoly = multiply_poly_terms(
                    poly,
                    trans[prev_size][cur_size],
                    R
                );

                if (ways != 1) {
                    for (int& x : npoly) x = mulmod(x, ways);
                }

                pair<int,int> key = {used + cur_size, cur_size};

                if (!ndp.count(key)) {
                    ndp[key] = move(npoly);
                } else {
                    for (int i = 0; i <= R; i++) {
                        ndp[key][i] = addmod(ndp[key][i], npoly[i]);
                    }
                }
            }
        }

        dp.swap(ndp);
    }

    int ans = 0;

    // LK には頂点 N が必ず入る
    for (auto& it : dp) {
        auto [used, prev_size] = it.first;
        const vector<int>& poly = it.second;

        int remain = mid - used;

        // t = LK に追加で入れる中間頂点数
        for (int t = 0; t <= remain; t++) {
            int cur_size = t + 1; // +1 は頂点 N

            if (trans[prev_size][cur_size].empty()) continue;

            int ways = C[remain][t];

            vector<int> poly2 = multiply_poly_terms(
                poly,
                trans[prev_size][cur_size],
                R
            );

            if (ways != 1) {
                for (int& x : poly2) x = mulmod(x, ways);
            }

            // B は距離 K より大きい、または到達不能な頂点
            int b = remain - t;

            // B 内部の辺と、B - LK 間の辺は自由
            int free_edges = b * (b - 1) / 2 + b * cur_size;

            vector<pair<int,int>> free_terms = one_plus_x_pow(free_edges);

            vector<int> poly3 = multiply_poly_terms(poly2, free_terms, R);

            ans = addmod(ans, poly3[R]);
        }
    }

    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, M, K;
    cin >> N >> M >> K;

    int E = N * (N - 1) / 2;

    if (E <= 22) {
        cout << brute(N, M, K) << '\n';
    } else {
        cout << solve_fast(N, M, K) << '\n';
    }

    return 0;
}
0