【THUPC2018】好图计数
题意
我们归纳定义一个无向简单图是好的:
- 一个单点是好的。
- 若干个好的图分别作为联通块所形成的图是好的。
- 一个好的图的补图是好的。
给定一个正整数 \(n\)。
求 \(n\) 个点的本质不同的好的图的数量对质数 \(P\) 取模的结果。
两个好的图的被认为是本质不同的,当且仅当无论如何将一个图重标号,它都不能与另一个图完全相同。
数据范围: $ T \leq 233, n \leq 23333$
题解
先推导一些关于好图的性质:
- 除了单个点之外,原图及其补图都是连通图的图不是好图。
这个结论非常显然。
- 一张图和它的补图不会同时为非连通图。
考虑一张非连通图的点 \((u, v)\), 如果原先不连通,则现在有边;如果原先连通,则存在一个点 \(k\) 在原图中和 \(u, v\) 都没有边,那么补图中边 \((u, k)\) 和 $ (v, k)$ 就存在。
这两个十分显然的结论就会推出一个极其有用的结论:
- 除了单个点之外,连通好图和非连通好图成对出现。
设 \(f_n\) 为 \(n\) 个点好图个数, \(n \not= 1\) 时, \(g_n\) 为连通好图个数,也为非连通好图个数。
连通好图可以划分为若干个非连通好图:
\[g_n = \prod_{i = 1} ^ {n - 1} (\sum_{j \geq 0} \binom{j + g_i - 1}{g_i - 1} x^{ij} ) [x^n]
\]
从而就可以得到 \(f\) 的 \(OGF\):
\[\begin{split}
F &=\prod_{i\geq 1}(\sum_{j\geq 0} \binom{j + g_i - 1}{g_i - 1} x ^ {ij})
\\ &= \prod_{i\geq 1} (1 - x^i) ^{ -g_i}
\end{split}
\]
两边取 \(ln\):
\[ln F = - \sum_{i \geq 1} g_i ln(1 - x^i)
\]
两边求导:
\[\frac{F'}{F} = \sum_{i \geq 1} g_i \frac{i x ^ {i - 1}}{1 - x^i}
\]
这就得到:
\[F' = F \sum_{i \geq 1} g_i \frac{i x ^ {i - 1}}{1 - x^i}
\\(n+1)f_{n+1} = \sum_{i = 0} ^ n f_{n-i} (\sum_{j \geq 1} g_j \frac{j x ^ {j - 1}}{1 - x^j} [x^{i}])
\]
因为有:
\[\frac{x^{j - 1}}{1 - x ^ j} = \sum_{i \leq 1} x^{ij-1}
\]
因此\(\frac{x^{j - 1}}{1 - x ^ j} [x_i] = [i | (j + 1)]\), 得到:
\[(n + 1) f_{n +1} = \sum_{i = 0} ^ n f_i \sum_{j | n + 1 - i} j g_j
\]
特判 $ i = 0$ 和 $ n = 1$,然后 \(O(n^2)\) 递推即可。
用 __int128 优化掉取模之后跑得飞快。
#pragma GCC optimize("2,Ofast,inline")
#include<bits/stdc++.h>
using namespace std;
const int N = 23334;
int n, m, mod;
int f[N], g[N], h[N];
const int Maxn = 23334;
int fac[Maxn], fav[Maxn], inv[Maxn];
void comb_init() {
fac[0] = fav[0] = 1;
inv[1] = fac[1] = fav[1] = 1;
for (int i = 2; i < Maxn; ++i) {
fac[i] = 1LL * fac[i - 1] * i % mod;
inv[i] = 1LL * -mod / i * inv[mod % i] % mod + mod;
fav[i] = 1LL * fav[i - 1] * inv[i] % mod;
}
}
inline void upd(int &x, int y) {
(x += y) >= mod ? x -= mod : 0;
}
inline int add(int x, int y) {
return (x += y) >= mod ? x - mod : x;
}
inline int dec(int x, int y) {
return (x -= y) < 0 ? x + mod : x;
}
void doit(int x) {
int del = 1LL * f[x] * x % mod;
for (int i = x; i < N; i += x) {
upd(h[i], del);
}
}
void solve() {
g[0] = 1;
g[1] = f[1] = 1;
doit(1);
for (int i = 2; i < N; ++i) {
__int128 tmp = 0;
for (int j = 1; j < i; ++j) {
tmp += (long long) g[j] * h[i - j];
}
for (int j = 1; j * j <= i; ++j) {
if (i % j == 0) {
tmp += (long long) j * f[j];
if (j * j != i) {
tmp += (long long) (i / j) * f[i / j];
}
}
}
g[i] = tmp % mod;
g[i] = 2LL * g[i] * inv[i] % mod;
f[i] = 1LL * g[i] * inv[2] % mod;
doit(i);
}
}
int main() {
int T;
cin >> T >> mod;
comb_init();
solve();
while (T--) {
scanf("%d", &n);
printf("%d\n", g[n] % mod);
}
return 0;
}