【luogu P4841】城市规划(NTT)

城市规划

题目链接:luogu P4841

题目大意

给你 n,要你求 n 个点简单有标号无向连通图的个数。

思路

g i g_i gi n n n 个点的简单有标号图个数, f i f_i fi 则是满足条件的。

g i = 2 ( i 2 ) g_i=2^{\binom{i}{2}} gi=2(2i)

f n = g n − ∑ i = 1 n − 1 ( n − 1 i − 1 ) f i g n − i f_n=g_n-\sum\limits_{i=1}^{n-1}\binom{n-1}{i-1}f_ig_{n-i} fn=gni=1n1(i1n1)figni

g n = ∑ i = 1 n ( n − 1 i − 1 ) f i g n − i g_n=\sum\limits_{i=1}^n\binom{n-1}{i-1}f_ig_{n-i} gn=i=1n(i1n1)figni

g n = ∑ i = 1 n ( n − 1 ) ! ( i − 1 ) ! ( n − i ) f i g n − i g_n=\sum\limits_{i=1}^n\dfrac{(n-1)!}{(i-1)!(n-i)}f_ig_{n-i} gn=i=1n(i1)!(ni)(n1)!figni

g n ( n − 1 ) ! = ∑ i = 1 n f i ( i − 1 ) ! g n − i ( n − i ) ! \dfrac{g_n}{(n-1)!}=\sum\limits_{i=1}^n\dfrac{f_i}{(i-1)!}\dfrac{g_{n-i}}{(n-i)!} (n1)!gn=i=1n(i1)!fi(ni)!gni

然后不难看出可以看成三个多项式,用个多项式求逆加多项式乘法即可。

代码

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define mo 1004535809
#define cpy(f, g, x) memcpy(f, g, sizeof(int) * (x))
#define clr(f, x) memset(f, 0, sizeof(int) * (x))

using namespace std;

const int N = 130000 * 8 + 5;
int n, g[N], g1[N], g2[N];
int jc[N], inv[N], invs[N];

int add(int x, int y) {return x + y >= mo ? x + y - mo : x + y;}
int dec(int x, int y) {return x < y ? x - y + mo : x - y;}
int mul(int x, int y) {return 1ll * x * y % mo;}
int ksm(int x, int y) {int re = 1; while (y) {if (y & 1) re = mul(re, x); x = mul(x, x); y >>= 1;} return re;}

struct Poly {
	int G, Gv, an[N];
	vector <int> Wn[21], Wn_[21];
	
	void Init() {
		G = 3; Gv = ksm(G, mo - 2);
		for (int mid = 1, d = 0; mid < N; mid <<= 1, d++) {
			int w = ksm(G, (mo - 1) / (mid << 1)), wv = ksm(Gv, (mo - 1) / (mid << 1));
			for (int j = 0, wn = 1, wn_ = 1; j < mid; j++, wn = mul(wn, w), wn_ = mul(wn_, wv)) {
				Wn[d].push_back(wn); Wn_[d].push_back(wn_); 
			}
		}
	}
	
	void get_an(int limit, int l_size) {
		for (int i = 0; i < limit; i++) an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1));
	}
	
	void NTT(int *f, int limit, int op) {
		for (int i = 0; i < limit; i++) if (i < an[i]) swap(f[i], f[an[i]]);
		for (int mid = 1, d = 0; mid < limit; mid <<= 1, d++)
			for (int R = mid << 1, j = 0; j < limit; j += R)
				for (int k = 0; k < mid; k++) {
					int x = f[j | k], y = mul(f[j | mid | k], (op == 1) ? Wn[d][k] : Wn_[d][k]);
					f[j | k] = add(x, y); f[j | mid | k] = dec(x, y);
				}
		if (op == -1) {
			int limv = ksm(limit, mo - 2);
			for (int i = 0; i < limit; i++) f[i] = mul(f[i], limv);
		}
	}
	
	void px(int *f, int *g, int limit) {
		for (int i = 0; i < limit; i++) f[i] = mul(f[i], g[i]);
	}
	
	void times(int *f, int *g, int n, int m, int T) {
		static int tmp[N];
		int limit = 1, l_size = 0; while (limit < n + m) limit <<= 1, l_size++;
		get_an(limit, l_size);
		clr(f + n, limit - n); cpy(tmp, g, m); clr(tmp + m, limit - m);
		NTT(f, limit, 1); NTT(tmp, limit, 1); px(f, tmp, limit); NTT(f, limit, -1);
		clr(f + T, limit - T); clr(tmp, limit);
	}
	
	void invp(int *f, int n) {
		static int w[N], r[N], tmp[N];
		w[0] = ksm(f[0], mo - 2);
		int limit = 1, l_size = 0;
		for (int len = 2; (len >> 1) <= n; len <<= 1) {
			limit = len; l_size++; get_an(limit, l_size);
			cpy(r, w, len >> 1);
			cpy(tmp, f, limit); NTT(tmp, limit, 1);
			NTT(r, limit, 1); px(r, tmp, limit);
			NTT(r, limit, -1); clr(r, limit >> 1);
			cpy(tmp, w, len); NTT(tmp, limit, 1);
			NTT(r, limit, 1); px(r, tmp, limit);
			NTT(r, limit, -1);
			for (int i = (len >> 1); i < len; i++)
				w[i] = dec(mul(w[i], 2), r[i]);
		}
		cpy(f, w, n); clr(w, limit); clr(r, limit); clr(tmp, limit);
	}
}P;

void Init() {
	P.Init();
	jc[0] = 1; for (int i = 1; i < N; i++) jc[i] = mul(jc[i - 1], i);
	inv[0] = inv[1] = 1; for (int i = 2; i < N; i++) inv[i] = mul(inv[mo % i], mo - mo / i);
	invs[0] = 1; for (int i = 1; i < N; i++) invs[i] = mul(invs[i - 1], inv[i]);
}

int main() {
	Init();
	
	scanf("%d", &n);
	for (int i = 0; i <= n; i++) g[i] = ksm(2, 1ll * i * (i - 1) / 2 % (mo - 1));
	for (int i = 1; i <= n; i++) g1[i] = mul(g[i], invs[i - 1]);
	for (int i = 0; i <= n; i++) g2[i] = mul(g[i], invs[i]);
	P.invp(g2, n + 1); P.times(g1, g2, n + 1, n + 1, n + 1);
	printf("%d", mul(g1[n], jc[n - 1]));
	
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值