けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AOJ 3169 Pyramid Graph (HUPC 2020 day1-F)

ゴリ (prd さん) と一緒に出て楽しかったコンテスト。
そして「グラフのサイクル・パスの列挙」に関する問題は北大セットの定番というイメージになってきた。

問題へのリンク

問題概要

下図のような  N 角錐が与えられる (底面が  N 角形で "頂点" の頂点番号を 0 として、 N 角形の頂点番号が  1, 2, \dots, N)。

頂点 0 から出発し、頂点 0 以外は同じ頂点を二度以上通ることなく頂点 0 へ戻ってくるサイクル (サーキットと呼ぶ) のうち、それに含まれる辺の本数が  K のものをすべて考えたときの、サーキットの長さ (辺の重みの合計) の総和を 998244353 で割ったあまりを求めよ。

f:id:drken1215:20200930135742p:plain

制約

  •  3 \le N \le 10^{6}
  •  3 \le K \le 2N

考えたこと

この手の問題の定石として、

「各辺について、それが何回カウントされるかを数え上げる」

というものがある。そして今回は

  • 側辺 (頂点 0 と  N 角形の各頂点をむすぶ辺)
  • 底辺 ( N 各形の辺)

は対称性からそれぞれの回数は等しい。さて、今回サーキットは「いくつかの頂点 0 から出発して頂点 0 へ戻ってくるサイクル」の集まりでできている。そのようなサイクルの個数  i を固定して数え上げることにする。

まず、サイクルの個数が  i であるような辺本数  K のサーキットの個数を  f(K, i) とすると、

  • 各側辺について、それが使われる回数は  f(K, i) \times \frac{2i}{N} となる
  • 各底辺について、それが使われる回数は  f(K, i) \times \frac{K - 2i}{N}

となる。これらの事実は「一つのサーキットをランダムに選んだときに特定の側辺や底辺が含まれる確率」を考えることで示せる。以上から、 f(K, i) を求める問題へと帰着された。

f(K, i) を求める

このような数珠状の構造をもつ対象物を数え上げるには、どこか 1 点を固定してあげる方法が有効だと思う。今回は

  •  i 個のサイクルのうちの最初のサイクルの位置関係

を固定するのが有力そう。まずその方法は  N 通りある。その後は

  • 各サイクルについて回る方向が  2 通りずつあるので、 2^{i} 通り
  •  i 個のサイクルのうち、最初の 1 個以外を巡る順序を決める方法が  (i-1)! 通り
  • 底辺を占める辺の本数は  K - 2i 本となるが、それを  i 分割する方法の数を考える。重複組合せを考えることで  C(K - 2i - 1, i - 1) 通りとわかる
  • 最後にそれらを配置する方法は、今度はサーキットに含まれない辺の本数が合計で  N - K + 2i 本あって、それを  i 分割する方法の数を求めればよい。重複組合せを考えることで  C(N - K + 2i - 1, i - 1) 通りとわかる

というふうになる。以上をまとめると、

 f(K, i) = N \times 2^{i} \times (i-1)! \times C(K - 2i - 1, i - 1) \times C(N - K + 2i - 1, i - 1)

と求められた。

まとめ

 i = 1, 2, \dots に対して、

  •  f(K, i) = N \times 2^{i} \times (i-1)! \times C(K - 2i - 1, i - 1) \times C(N - K + 2i - 1, i - 1) として
  • (側辺の長さの総和)  \times f(K, i) \times \frac{2i}{N} を合算
  • (底辺の長さの総和)  \times f(K, i) \times \frac{K-2i}{N} を合算

すれば OK。計算量は  O(N) となる。

コード

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

// modint: mod 計算を int を扱うように扱える構造体
template<int MOD> struct Fp {
    long long val;
    constexpr Fp(long long v = 0) noexcept : val(v % MOD) {
        if (val < 0) val += MOD;
    }
    constexpr int getmod() { return MOD; }
    constexpr Fp operator - () const noexcept {
        return val ? MOD - val : 0;
    }
    constexpr Fp operator + (const Fp& r) const noexcept { return Fp(*this) += r; }
    constexpr Fp operator - (const Fp& r) const noexcept { return Fp(*this) -= r; }
    constexpr Fp operator * (const Fp& r) const noexcept { return Fp(*this) *= r; }
    constexpr Fp operator / (const Fp& r) const noexcept { return Fp(*this) /= r; }
    constexpr Fp& operator += (const Fp& r) noexcept {
        val += r.val;
        if (val >= MOD) val -= MOD;
        return *this;
    }
    constexpr Fp& operator -= (const Fp& r) noexcept {
        val -= r.val;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr Fp& operator *= (const Fp& r) noexcept {
        val = val * r.val % MOD;
        return *this;
    }
    constexpr Fp& operator /= (const Fp& r) noexcept {
        long long a = r.val, b = MOD, u = 1, v = 0;
        while (b) {
            long long t = a / b;
            a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        val = val * u % MOD;
        if (val < 0) val += MOD;
        return *this;
    }
    constexpr bool operator == (const Fp& r) const noexcept {
        return this->val == r.val;
    }
    constexpr bool operator != (const Fp& r) const noexcept {
        return this->val != r.val;
    }
    friend constexpr ostream& operator << (ostream &os, const Fp<MOD>& x) noexcept {
        return os << x.val;
    }
    friend constexpr Fp<MOD> modpow(const Fp<MOD> &a, long long n) noexcept {
        if (n == 0) return 1;
        auto t = modpow(a, n / 2);
        t = t * t;
        if (n & 1) t = t * a;
        return t;
    }
};

// 二項係数ライブラリ
template<class T> struct BiCoef {
    vector<T> fact_, inv_, finv_;
    constexpr BiCoef() {}
    constexpr BiCoef(int n) noexcept : fact_(n, 1), inv_(n, 1), finv_(n, 1) {
        init(n);
    }
    constexpr void init(int n) noexcept {
        fact_.assign(n, 1), inv_.assign(n, 1), finv_.assign(n, 1);
        int MOD = fact_[0].getmod();
        for(int i = 2; i < n; i++){
            fact_[i] = fact_[i-1] * i;
            inv_[i] = -inv_[MOD%i] * (MOD/i);
            finv_[i] = finv_[i-1] * inv_[i];
        }
    }
    constexpr T com(int n, int k) const noexcept {
        if (n < k || n < 0 || k < 0) return 0;
        return fact_[n] * finv_[k] * finv_[n-k];
    }
    constexpr T fact(int n) const noexcept {
        if (n < 0) return 0;
        return fact_[n];
    }
    constexpr T inv(int n) const noexcept {
        if (n < 0) return 0;
        return inv_[n];
    }
    constexpr T finv(int n) const noexcept {
        if (n < 0) return 0;
        return finv_[n];
    }
};

const int MOD = 998244353;
using mint = Fp<MOD>;

int main() {
    int N, K;
    cin >> N >> K;
    BiCoef<mint> bc(N*2 + 1);
    mint A = 0, B = 0;
    for (int i = 0; i < N; ++i) { long long v; cin >> v; A += v; }
    for (int i = 0; i < N; ++i) { long long v; cin >> v; B += v; }
    vector<mint> two(N+5, 1);
    for (int i = 1; i < two.size(); ++i) two[i] = two[i-1] * 2;

    mint res = 0;
    for (int i = 1; i*2 <= K; ++i) {
        mint fac = bc.com(K - i*2 - 1, i - 1) *
                   bc.com(N - K + i*2 - 1, i - 1) *
                   bc.fact(i - 1) * two[i];
        mint tmp = fac * (A * i*2 + B * (K - i*2));
        res += tmp;
    }
    cout << res << endl;
}