また一つ、プリューファーコードの練習問題が増えた!
問題概要
正の整数値 が与えられる。
頂点数 の重み付き木であって、以下の条件を満たすものの個数を 1000000007 で割った余りを求めよ。
- 各辺の重みは 以上 以下の整数値である
- 2 頂点 間の距離はちょうど である ( は given)
制約
考えたこと
対称性より、2 頂点は であるとしてよい。まず、2 頂点 を結ぶパス上の頂点数 で場合分けして考えることにする。まず、重複組合せの考え方より、
- パスに来る頂点番号の並べ方: 通り
- パス上の辺の重みの決め方: 通り
- パス以外の辺の重みの決め方: 通り
となる。残りは、パスの形を残しつつ、全域木の作り方が何通りあるかを考えればよい。
プリューファーコードを用いた考察
プリューファーコード自体はこの記事にて。
まず、パス上の頂点番号は であるとしても一般性を失わない。このパスを とする。このようにしたとき、プリューファーコードの手続きを考えると、最後にパス の部分が残ることになる。
よって、全域木のプリューファーコードとしてありうるものは、次のようになる。
- 最初の 個の要素については、 の任意の値をとりうる (パス と残り 1 個の頂点になるまで)
- その次の値については、パス上のいずれかの頂点が来るので、 通りありうる (最後にパス以外の葉を除去してパス のみが残る瞬間)
- その後は固定値となる
よって、パスと重みを決めた後の全域木の作り方は 通りとなる。例外として の場合は 通りである。
以上より、 の解法が得られた。
コード
#include <bits/stdc++.h> using namespace std; // modint template<int MOD> struct Fp { // inner value long long val; // constructor constexpr Fp() noexcept : val(0) { } constexpr Fp(long long v) noexcept : val(v % MOD) { if (val < 0) val += MOD; } constexpr long long get() const noexcept { return val; } constexpr int get_mod() const noexcept { return MOD; } // arithmetic operators 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 Fp pow(long long n) const noexcept { Fp res(1), mul(*this); while (n > 0) { if (n & 1) res *= mul; mul *= mul; n >>= 1; } return res; } constexpr Fp inv() const noexcept { Fp res(1), div(*this); return res / div; } // other operators 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 istream& operator >> (istream &is, Fp<MOD> &x) noexcept { is >> x.val; x.val %= MOD; if (x.val < 0) x.val += MOD; return is; } friend constexpr ostream& operator << (ostream &os, const Fp<MOD> &x) noexcept { return os << x.val; } friend constexpr Fp<MOD> modpow(const Fp<MOD> &r, long long n) noexcept { return r.pow(n); } friend constexpr Fp<MOD> modinv(const Fp<MOD> &r) noexcept { return r.inv(); } }; // Binomial coefficient 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].get_mod(); 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]; } }; int main() { int N, M, A, B; cin >> N >> M >> A >> B; const int MOD = 1000000007; using mint = Fp<MOD>; BiCoef<mint> bc(max(N, M) + 10); mint res = 0; // 頂点 a, b を結ぶパス上の頂点数 k で場合分け for (int k = 2; k <= N; ++k) { // パスに来る頂点番号の並べ方 mint path_order = bc.com(N-2, k-2) * bc.fact(k-2); // パス上の重さの選び方 mint path_weight = bc.com(M-1, k-2); // 残りの頂点で全域木を作る方法:プリューファーコードより mint rem_tree = mint(N).pow(N-k-1) * (k != N ? k : 1); // 残りの辺の重みの選び方 mint rem_weight = mint(M).pow(N-k); // 集計 res += path_order * path_weight * rem_tree * rem_weight; } cout << res << endl; }