【普通生成函数】[集训队作业2013]城市规划(简单有标号无向连通图计数)

题目

https://www.luogu.com.cn/problem/P4841

思路

f ( n ) f(n) f(n) n n n 个点的简单有标号无向连通图数目, g ( n ) g(n) g(n) 为简单有标号无向图数目。那么显然有 g ( n ) = 2 C n 2 = ∑ i = 1 n C n − 1 i − 1 f ( i ) g ( n − i ) g(n)=2^{C_n^2}=\sum_{i=1}^nC_{n-1}^{i-1}f(i)g(n-i) g(n)=2Cn2=i=1nCn1i1f(i)g(ni)
C n − 1 i − 1 C_{n-1}^{i-1} Cn1i1 展开并用 2 C i 2 2^{C_i^2} 2Ci2 代替 g ( i ) g(i) g(i)
2 c n 2 ( n − 1 ) ! = ∑ i = 1 n f ( i ) ( i − 1 ) ! g ( n − i ) ( n − i ) ! \frac{2^{c_n^2}}{(n-1)!}=\sum_{i=1}^n\frac{f(i)}{(i-1)!}\frac{g(n-i)}{(n-i)!} (n1)!2cn2=i=1n(i1)!f(i)(ni)!g(ni)
可以发现这是一个卷积
可以表示成 H = F ∗ G H=F*G H=FG
F = H ∗ G − 1 F=H*G^{-1} F=HG1

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=(1<<19)+77,mod=1004535809;
const ll g=3;
int r[N];ll a0[N];
ll power(ll x,ll t)
{
	ll b=1;
	while(t)
	{
		if(t&1) b=b*x%mod; x=x*x%mod; t>>=1;
	}
	return b;
}
void dft(ll *a,int n,int f)
{
	for(int i=1; i<=n; i++) if(i<r[i]) swap(a[i],a[r[i]]);
	for(int i=2; i<=n; i<<=1)
	{
		int now=i>>1; ll wn=power(g,(mod-1)/i);
		if(f==-1) wn=power(wn,mod-2);
		for(int j=0; j<n; j+=i)
		{
			ll x,y,w=1;
			for(int k=j; k<j+now; k++,w=w*wn%mod)
			{
				x=a[k],y=w*a[k+now]%mod;
				a[k]=(x+y)%mod,a[k+now]=(x-y+mod)%mod;
			}
		}
	}
	ll invn=power(n,mod-2);
	if(f==-1) for(int i=0; i<n; i++) a[i]=a[i]*invn%mod;
}
void ginv(int n,ll *a,ll *b)
{
	if(n==1)
	{
		b[0]=power(a[0],mod-2); return;
	}
	ginv(n>>1,a,b);
	int len=1,lg=0;
	while(len<n<<1) len<<=1,lg++;
	for(int i=1; i<=len; i++) r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));
	for(int i=0; i<n; i++) a0[i]=a[i];
	for(int i=n; i<len; i++) a0[i]=0;
	dft(b,len,1); dft(a0,len,1);
	for(int i=0; i<len; i++) 
		b[i]=b[i]*((2-a0[i]*b[i]%mod+mod)%mod)%mod;
	dft(b,len,-1);
//	for(int i=0; i<len; i++) b0[i]=0;
	for(int i=n; i<len; i++) b[i]=0;
}
ll inv[N],unfac[N],c2[N],gg[N],h[N],f[N],G[N],ans;
int n;
void init(int n)
{
	inv[1]=unfac[1]=unfac[0]=1;
	for(int i=2; i<=n; i++) inv[i]=inv[mod%i]*(mod-mod/i)%mod,unfac[i]=unfac[i-1]*inv[i]%mod;
	c2[1]=1;
	for(ll i=2; i<=n; i++) c2[i]=power(2,i*(i-1)/2%(mod-1));
	gg[0]=1;
	for(int i=1; i<=n; i++) gg[i]=unfac[i]*c2[i]%mod,h[i]=unfac[i-1]*c2[i]%mod;
}
int main()
{
	scanf("%d",&n);
	init(n);
	int len=1,lg=0;
	while(len<=n) len<<=1,lg++;
	ginv(len,gg,G);
	len<<=1; lg++;
	for(int i=1; i<=len; i++) r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));
	dft(h,len,1); dft(G,len,1);
	for(int i=0; i<len; i++) f[i]=h[i]*G[i]%mod;
	dft(f,len,-1);
	ll fac=1;
	for(ll i=1; i<n; i++) fac=fac*i%mod;
	ans=f[n]*fac%mod;
	printf("%lld",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值