【THUPC 2018】好图计数

Problem

Description

这道题目非常简单,它甚至没有题目背景、没有任何故事。

但为了能让你顺利理解题目,善良的 Yazid 将为你介绍一些概念。

  • 简单图:不存在重边、自环的图。(重边即为两条完全相同的边,自环即为两端点为同一节点的边)

  • 补图:一个图 \(G\) 的补图有与 \(G\) 完全相同的节点,且任意两点之间有边当且仅当他们在 \(G\) 中不相邻。

我们归纳定义一个无向简单图是好的

  1. 一个单点是好的。

  2. 若干个好的图分别作为联通块所形成的图是好的。

  3. 一个好的图的补图是好的。

总共 \(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\) 的联通好图,其能够组成的好图方案的生成函数为:

\[(\sum_i x^{ik})^{g_k}=(1-x^k)^{-g_k} \]

则我们可以列出 \(\{f_n\}\) 的生成函数 \(F\) 的式子:

\[F=\prod_{k} (1-x^k)^{-g_k} \]

发现右边是 \(\prod\) 特别不好搞,于是考虑用 \(ln\) 拆成加法,然后再求导去掉 \(ln\)

\[lnF=\sum_k (ln(1-x^k)^{-g_k})\\ \frac{F'}{F}=\sum_k g_k*k*\frac{x^{k-1}}{1-x^k}\\ F'=F*\sum_k g_k*k*\frac{x^{k-1}}{1-x^k} \]

接下来我们要推递推用的式子了:

\[[x^n]F'=(n+1)f_{n+1}=[x^n]F*(\sum_k g_k*k*\frac{x^{k-1}}{1-x^k})\\ =\sum_{i=0}^n f_i*[x^{n-i}](\sum_k g_k*k*\frac{x^{k-1}}{1-x^k}) \]

对于 \(\frac{x^{k-1}}{1-x^k}\) 来说,考虑 \(\frac{1}{1-x^k}=\sum_{i>=0}x^{ik}\) ,则有:

\[\frac{x^{k-1}}{1-x^k}=\sum_{i>=1}x^{ik-1} \]

故可得:

\[[x^{n-i}](\sum_k g_k*k*\frac{x^{k-1}}{1-x^k})=\sum_{k|(n-i+1)} kg_k \]

然后我们设 \(h_i=\sum_{j|i} j*g_j\)

代回原式便有:

\[(n+1)f_{n+1}=\sum_{i=0}^n f_i * h(n-i+1) \]

发现由于在计算 \(f_0*h(n+1)\) 的时候式中包含未知的 \(g_{n+1}=\frac{f_{n+1}}{2}\) ,所以将其移到左边去,则式子变为:

\[\frac{(n+1)}{2}f_{n+1}=\sum_{i=1}^n f_i * h(n-i+1)+\sum_{k|(n+1),k<n+1} k*g(k) \]

因为 \(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]);
  }
}
posted @ 2020-05-14 18:17  洛水·锦依卫  阅读(243)  评论(0编辑  收藏  举报