Processing math: 100%

【THUPC 2018】好图计数

Problem#

Description#

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

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

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

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

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

  1. 一个单点是好的。

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

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

总共 T 组数据。

每组数据给定一个正整数 n

n 个点的本质不同的好的图的数量对质数 P 取模的结果。

两个好的图的被认为是本质不同的,当且仅当无论如何将一个图重标号,它都不能与另一个图完全相同。

Range#

T233,n23333,229<P<230 ,且 P 为质数。

Algorithm#

生成函数,DP

Mentality#

典型的利用生成函数寻找性质。

根据题目这个奇怪的定义,我们可以得到以下结论:

不难发现,当超过一个点的时候,一个联通图要成为好图,必须依靠条件 3

同时,对于一个不联通的图,它的补图一定是个联通图。证明很简单,对于任意两个联通块 AB ,在补图里,A 中的每个点都会向 B 中的每个点连边,则两个联通块自然就联通了。

则我们的联通好图和不联通好图一定可以成对两两互补。

所以设 fnn 个点的好图个数,gnn 个点的联通好图个数,则有:fn=2gn

为了推式子比较方便,我们设 f0=1

不难发现,对于大小为 k 的联通好图,其能够组成的好图方案的生成函数为:

(ixik)gk=(1xk)gk

则我们可以列出 {fn} 的生成函数 F 的式子:

F=k(1xk)gk

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

lnF=k(ln(1xk)gk)FF=kgkkxk11xkF=Fkgkkxk11xk

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

[xn]F=(n+1)fn+1=[xn]F(kgkkxk11xk)=ni=0fi[xni](kgkkxk11xk)

对于 xk11xk 来说,考虑 11xk=i>=0xik ,则有:

xk11xk=i>=1xik1

故可得:

[xni](kgkkxk11xk)=k|(ni+1)kgk

然后我们设 hi=j|ijgj

代回原式便有:

(n+1)fn+1=ni=0fih(ni+1)

发现由于在计算 f0h(n+1) 的时候式中包含未知的 gn+1=fn+12 ,所以将其移到左边去,则式子变为:

(n+1)2fn+1=ni=1fih(ni+1)+k|(n+1),k<n+1kg(k)

因为 O2 很猛,直接 n2 卡常递推就可以过了。

至于怎么卡常……什么 FastMod 加速取模都是假的,真正快到极致的就是不取模,用 int128 省去大量取模,你值得拥有。

Code#

Copy
#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 @   洛水·锦依卫  阅读(246)  评论(0编辑  收藏  举报
编辑推荐:
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
阅读排行:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 用 C# 插值字符串处理器写一个 sscanf
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!
点击右上角即可分享
微信分享提示

目录

目录

×