【THUPC 2018】好图计数
Problem
Description
这道题目非常简单,它甚至没有题目背景、没有任何故事。
但为了能让你顺利理解题目,善良的 Yazid 将为你介绍一些概念。
-
简单图:不存在重边、自环的图。(重边即为两条完全相同的边,自环即为两端点为同一节点的边)
-
补图:一个图 \(G\) 的补图有与 \(G\) 完全相同的节点,且任意两点之间有边当且仅当他们在 \(G\) 中不相邻。
我们归纳定义一个无向简单图是好的:
-
一个单点是好的。
-
若干个好的图分别作为联通块所形成的图是好的。
-
一个好的图的补图是好的。
总共 \(T\) 组数据。
每组数据给定一个正整数 \(n\)。
求 \(n\) 个点的本质不同的好的图的数量对质数 \(P\) 取模的结果。
两个好的图的被认为是本质不同的,当且仅当无论如何将一个图重标号,它都不能与另一个图完全相同。
Range
\(T\le 233, n\le 23333, 2^{29}< P< 2^{30}\) ,且 \(P\) 为质数。
Algorithm
生成函数,\(DP\) 。
Mentality
典型的利用生成函数寻找性质。
根据题目这个奇怪的定义,我们可以得到以下结论:
不难发现,当超过一个点的时候,一个联通图要成为好图,必须依靠条件 \(3\) 。
同时,对于一个不联通的图,它的补图一定是个联通图。证明很简单,对于任意两个联通块 \(A\) 和 \(B\) ,在补图里,\(A\) 中的每个点都会向 \(B\) 中的每个点连边,则两个联通块自然就联通了。
则我们的联通好图和不联通好图一定可以成对两两互补。
所以设 \(f_n\) 为 \(n\) 个点的好图个数,\(g_n\) 为 \(n\) 个点的联通好图个数,则有:\(f_n=2g_n\) 。
为了推式子比较方便,我们设 \(f_0=1\) 。
不难发现,对于大小为 \(k\) 的联通好图,其能够组成的好图方案的生成函数为:
则我们可以列出 \(\{f_n\}\) 的生成函数 \(F\) 的式子:
发现右边是 \(\prod\) 特别不好搞,于是考虑用 \(ln\) 拆成加法,然后再求导去掉 \(ln\)。
接下来我们要推递推用的式子了:
对于 \(\frac{x^{k-1}}{1-x^k}\) 来说,考虑 \(\frac{1}{1-x^k}=\sum_{i>=0}x^{ik}\) ,则有:
故可得:
然后我们设 \(h_i=\sum_{j|i} j*g_j\)
代回原式便有:
发现由于在计算 \(f_0*h(n+1)\) 的时候式中包含未知的 \(g_{n+1}=\frac{f_{n+1}}{2}\) ,所以将其移到左边去,则式子变为:
因为 \(O2\) 很猛,直接 \(n^2\) 卡常递推就可以过了。
至于怎么卡常……什么 \(FastMod\) 加速取模都是假的,真正快到极致的就是不取模,用 \(int128\) 省去大量取模,你值得拥有。
Code
#include <cstdio>
#include <iostream>
using namespace std;
#define LL long long
#define go(G, x, i, v) \
for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]])
#define inline __inline__ __attribute__((always_inline))
inline LL read() {
LL x = 0, w = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') w = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}
return x * w;
}
const int Max_n = 3e4 + 5;
int T, mod, n;
int f[Max_n], g[Max_n], h[Max_n];
namespace Init {
int ksm(int a, int b = mod - 2) {
int res = 1;
for (; b; b >>= 1, a = (LL)a * a % mod)
if (b & 1) res = (LL) res * a % mod;
return res;
}
void main() {
n = 23333;
for (int i = 1; i <= n; i++) {
__int128 t = 0;
for (int j = 1; j < i; j++) t += (LL)f[j] * h[i - j];
f[i] = 2ll * (f[i] + t % mod) * ksm(i) % mod;
g[i] = (LL)ksm(2) * f[i] % mod + (i == 1), f[i] += (i == 1);
for (int j = i; j <= n; j += i) {
(h[j] += (LL)i * g[i] % mod) %= mod;
if (j > i) (f[j] += (LL)i * g[i] % mod) %= mod;
}
}
}
} // namespace Init
int main() {
#ifndef ONLINE_JUDGE
freopen("6389.in", "r", stdin);
freopen("6389.out", "w", stdout);
#endif
T = read(), mod = read();
Init::main();
while(T--) {
n = read();
printf("%d\n", f[n]);
}
}