P4841 [集训队作业2013]城市规划 解题报告

P4841 [集训队作业2013]城市规划 解题报告

link

题目大意

n n n 个点的简单有标号无向连通图的数目。

解法1

我们设 n n n 个点的简单有标号无向连通图的数目为 f n f_n fn n n n 个点的简单有标号无向图(不一定连通)的数目为 g n g_n gn

那么 g n g_n gn 是相当好算的。考虑每两点之间边的有无,有 g n = 2 C n 2 g_n=2^{\mathrm C_n^2} gn=2Cn2

一个常用的套路,我们枚举1号点所在的连通块大小。
g n = ∑ x = 1 n ( n − 1 x − 1 ) f x g n − x g_n=\sum_{x=1}^n\left(\begin{matrix}n-1 \\ x-1\end{matrix}\right)f_xg_{n-x} gn=x=1n(n1x1)fxgnx
这个组合数是为了选标号。

把组合数拆了,简单变形可得
g n ( n − 1 ) ! = ∑ x + y = n f x ( x − 1 ) ! g y y ! \dfrac {g_n}{(n-1)!}=\sum_{x+y=n}\dfrac{f_x}{(x-1)!}\dfrac{g_y}{y!} (n1)!gn=x+y=n(x1)!fxy!gy
g n g_n gn 的 EGF 为 G ( x ) = ∑ n ≥ 0 g n x n n ! G(x)=\sum\limits_{n\ge 0}g_n\dfrac{x^n}{n!} G(x)=n0gnn!xn f n f_n fn 的 EGF 为 F ( x ) = ∑ n ≥ 0 f n x n n ! F(x)=\sum\limits_{n\ge0}f_n\dfrac{x^n}{n!} F(x)=n0fnn!xn,可得
x G ′ ( x ) = x F ′ ( x ) ∗ G ( x ) ∴ F ( x ) = ∫ 0 x G ′ ( x ) G ( x ) = ln ⁡ G ( x ) xG'(x)=xF'(x)*G(x)\\ \therefore F(x)=\int_0^x \dfrac{G'(x)}{G(x)}=\ln G(x) xG(x)=xF(x)G(x)F(x)=0xG(x)G(x)=lnG(x)
直接计算 G ( x ) G(x) G(x) 系数再套一个板子即可。

这是一种用生成函数快速计算递推式的方法。

解法2

由于是有标号计数,我们直接设EGF。设 F ( x ) F(x) F(x) 是简单有标号无向连通图的EGF, G ( x ) G(x) G(x) 是简单有标号无向图的EGF。

那么 G ( x ) G(x) G(x) 的各项系数是很好求的(同上)。

我们考虑普通的无向图是由若干连通块“拼接”得到的,于是我们可以枚举连通块数量。
G ( x ) = ∑ k ≥ 0 F k ( x ) k ! = exp ⁡ F ( x ) G(x)=\sum_{k\ge 0}\dfrac{F^k(x)}{k!}=\exp F(x) G(x)=k0k!Fk(x)=expF(x)
这里 F k ( x ) F^k(x) Fk(x) 是因为是 k k k 个连通块组成的图,而 k ! k! k! 是为了消除连通块之间的顺序。

那么显然 F ( x ) = ln ⁡ G ( x ) F(x)=\ln G(x) F(x)=lnG(x)

这里直接使用了生成函数完成组合对象之间关系的推导。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
ll read() {
	ll x = 0, f = 1; char ch = getchar();
	for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + int(ch - '0');
	return x * f;
}
#define clr(f, s, e) memset(f + (s), 0x00, sizeof(ll) * ((e) - (s)))
#define cpy(f, g, n) memcpy(g, f, sizeof(ll) * (n))
const int MAXN = (1 << 18)+5, P = 1004535809, G = 3, invG = 334845270;
ll pls(ll a, ll b) {return a + b < P ? a + b : a + b - P;}
ll mns(ll a, ll b) {return a < b ? a + P - b : a - b;}
ll mul(ll a, ll b) {return a * b % P;}
ll qpow(ll a, ll n) {ll ret = 1; for(; n; n >>= 1, a = mul(a, a)) if(n & 1) ret = mul(ret, a); return ret;}
ll fac[MAXN], ifac[MAXN], inv[MAXN], _g[2][MAXN], tr[MAXN], pow2[MAXN], tf;
void init() {
	fac[0] = 1; for(int i = 1; i < MAXN; i++) fac[i] = mul(fac[i-1], i);
	ifac[MAXN-1] = qpow(fac[MAXN-1], P-2); for(int i = MAXN-2; i >= 0; i--) ifac[i] = mul(ifac[i+1], i+1);
	inv[1] = 1; for(int i = 2; i < MAXN; i++) inv[i] = mul(P - P / i, inv[P % i]);
	for(int l = 2; l < MAXN; l <<= 1) _g[1][l] = qpow(G, (P-1) / l), _g[0][l] = qpow(invG, (P-1) / l);
}
int getlim(int n) {
	int lim = 1; for(; lim < n + n; lim <<= 1);
	return lim;
}
void tpre(int lim) {
	if(lim == tf) return;
	tf = lim; for(int i = 0; i < lim; i++) tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? (lim >> 1) : 0);
}
void NTT(ll* f, int lim, int fl) {
	tpre(lim); for(int i = 0; i < lim; i++) if(i < tr[i]) swap(f[i], f[tr[i]]);
	for(int l = 2, k = 1; l <= lim; l <<= 1, k <<= 1)
		for(int i = 0; i < lim; i += l) {
			ll gn = 1;
			for(int j = i; j < i+k; j++, gn = mul(gn, _g[fl][l])) {
				ll tt = mul(f[j+k], gn);
				f[j+k] = mns(f[j], tt);
				f[j] = pls(f[j], tt);
			}
		}
	if(!fl) {
		for(int i = 0; i < lim; i++) f[i] = mul(f[i], inv[lim]);
	}
}
void Mul(ll* f, ll* g, ll* h, int n) {
	static ll a[MAXN], b[MAXN];
	int lim = getlim(n);
	cpy(f, a, n); clr(a, n, lim);
	cpy(g, b, n); clr(b, n, lim);
	NTT(a, lim, 1); NTT(b, lim, 1);
	for(int i = 0; i < lim; i++) h[i] = mul(a[i], b[i]);
	NTT(h, lim, 0); clr(h, n, lim);
}
void Inv(ll* f, ll* g, int n) {
	static ll a[MAXN];
	if(n == 1) {g[0] = qpow(f[0], P-2); return;}
	Inv(f, g, (n+1) >> 1);
	int lim = getlim(n);
	clr(g, (n+1) >> 1, lim);
	cpy(f, a, n); clr(a, n, lim);
	NTT(g, lim, 1); NTT(a, lim, 1);
	for(int i = 0; i < lim; i++) g[i] = (2 - a[i] * g[i] % P + P) * g[i] % P;
	NTT(g, lim, 0); clr(g, n, lim);
}
void Deriv(ll* f, ll* g, int n) {
	for(int i = 1; i < n; i++) g[i-1] = mul(f[i], i);
	g[n-1] = 0;
}
void Integ(ll* f, ll* g, int n) {
	for(int i = 1; i < n; i++) g[i] = mul(f[i-1], inv[i]);
	g[0] = 0;
}
void Ln(ll* f, ll* g, int n) {
	static ll a[MAXN], b[MAXN];
	Inv(f, a, n); Deriv(f, b, n);
	Mul(a, b, a, n); Integ(a, g, n);
}
int n;
ll f[MAXN], g[MAXN];
int main() {
	init();
	n = read();
	for(int i = 0; i <= n; i++) g[i] = mul(qpow(2, 1ll * i * (i-1) / 2), ifac[i]);
	Ln(g, f, n+1);
	printf("%lld\n", mul(f[n], fac[n]));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值