残り 1 分でなんとか通せた。
問題概要
頂点数 の連結な単純無向グラフ (頂点番号は であって、次の条件を満たすものの個数を で割ったあまりを求めよ。
なお、ここで、頂点 から各頂点 までの最短距離を としている。
制約
考えたこと
グラフの個数を数える系の問題はやっぱり難しい!
最初は、頂点 の値で場合分けすることを考えた。 のとき、のこりの 個の頂点のうちのいくつかは 値が になるはずだ。そうやっていくと、再帰的に求められそうな雰囲気を感じた。
そこで、次の再帰関数を考えた。元の問題と異なり、始点を としている。
← 頂点のグラフを考える。頂点 から頂点 への最短距離を としたとき、
が成り立つようなものの個数
つまり、最短距離が となる頂点がちょうど 個あるようなものの個数を考えるのだ。答えは と表せる。
本番はこのまま考察を進めていったが、このままだと の解法となる。しかし自分が作った DP の遷移式を見ると、実は の値に依存しないことに気づいた。つまり、上の関数の定義から を削って良い!!
整理して、次の関数を求める。
← 頂点のグラフを考える。頂点 から頂点 への最短距離を とする。また とおく
が成り立つようなものの個数
遷移を詰める
頂点 のうち、最短距離が であるものの個数 で場合分けする。
このとき、求めるグラフは次の性質を満たす。ここで、頂点 をグループ P、残りの頂点のうち最短距離が である 個の頂点をグループ Q、それ以外をグループ R と呼んでいる。
- グループ P の各頂点 に対して、グループ Q の頂点 が少なくとも 1 つ存在して、辺 が貼られている
- グループ P の頂点とグループ R の頂点との間に辺はない
これを踏まえて、数え上げる。
- グループ P 同士の辺について: 通り
- グループ P とグループ Q(, R) の間の辺について: 通り
- グループ Q, R 同士の辺について: 通り
よって、
となります。これを DP として実装すると の計算量となる。
コード
#include <bits/stdc++.h> using namespace std; // mod int MOD; // 二項係数 const int MAX_C = 1000; long long Com[MAX_C][MAX_C]; void COMinit() { Com[0][0] = 1; for (int i = 1; i < MAX_C; ++i) Com[0][i] = 0; for (int i = 1; i < MAX_C; ++i) { Com[i][0] = 1; for (int j = 1; j < MAX_C; ++j) { Com[i][j] = (Com[i-1][j-1] + Com[i-1][j]) % MOD; } } } long long COM(int n, int k){ if (n < k) return 0; if (n < 0 || k < 0) return 0; return Com[n][k]; } // two[k]:2^k、coef[k][a]:(2^k-1)^a vector<long long> two; vector<vector<long long>> coef; // DP vector<vector<long long>> dp; long long func(int n, int a) { if (n == 1) return (a == 0); if (a == 0) return (n == 1); if (dp[n][a] != -1) return dp[n][a]; long long res = 0; for (int k = 0; k <= n-a-1; ++k) { long long cur = func(n-a, k); cur = cur * two[a*(a-1) / 2] % MOD; cur = cur * Com[n-a-1][k] % MOD; cur = cur * coef[k][a] % MOD; res = (res + cur) % MOD; } return dp[n][a] = res; } int main() { // 入力 int N; cin >> N >> MOD; // 二項係数などの準備 COMinit(); two.assign(N * N + 1, 1); for (int k = 0; k < N * N; ++k) two[k + 1] = two[k] * 2 % MOD; coef.assign(N + 1, vector<long long>(N + 1, 1)); for (int k = 1; k <= N; ++k) { long long base = (two[k] + MOD - 1) % MOD; for (int a = 1; a <= N; ++a) { coef[k][a] = coef[k][a-1] * base % MOD; } } // DP dp.assign(N + 1, vector<long long>(N + 1, -1)); cout << func(N, 1) << endl; }
形式的冪級数
を使う解法があるらしい。いずれちゃんと理解したい