【洛谷P4841】城市规划

题目

题目链接:https://www.luogu.com.cn/problem/P4841
刚刚解决完电力网络的问题,阿狸又被领导的任务给难住了。
刚才说过,阿狸的国家有 \(n\) 个城市,现在国家需要在某些城市对之间建立一些贸易路线,使得整个国家的任意两个城市都直接或间接的连通。
为了省钱, 每两个城市之间最多只能有一条直接的贸易路径。对于两个建立路线的方案,如果存在一个城市对,在两个方案中是否建立路线不一样,那么这两个方案就是不同的,否则就是相同的。现在你需要求出一共有多少不同的方案。
好了,这就是困扰阿狸的问题。换句话说,你需要求出 \(n\) 个点的简单 (无重边无自环) 有标号无向连通图数目。
由于这个数字可能非常大, 你只需要输出方案数对 \(1004535809\) ( \(479 \times 2 ^{21} + 1\) ) 即可。
\(n\leq 130000\)。

思路

【洛谷P4841】城市规划

QuantAsk 和 my_dog 太强啦 orz。


设 \(f_i\) 表示 \(i\) 个点的无向连通图数量,\(g_i\) 表示 \(i\) 个点的无向图数量。
因为每一条边可选可不选,所以显然

\[g_i=2^{\frac{n(n-1)}{2}}=2^{\binom{n}{2}} \]

考虑枚举点 \(1\) 所在联通块大小,不难得到

\[g_i=\sum^{i}_{j=1}\binom{i}{j}f_jg_{i-j} \]

\[2^{\binom{n}{2}}=\sum^{n}_{i=1}\binom{n}{i}f_i\times 2^{\binom{n-i}{2}} \]

组合数拆开,移项

\[\frac{2^{\binom{n}{2}}}{(n-1)!}=\sum^{n}_{i=1}\frac{f_i}{(i-1)!}\times \frac{2^{\binom{n-i}{2}}}{(n-i)!} \]

设 \(F(x)=\sum^{+\infty}_{n=1}\frac{2^{\binom{n}{2}}}{(n-1)!}\),\(G(x)=\sum^{+\infty}_{n=1}\sum^{n}_{i=1}\frac{f_i}{(i-1)!}\),\(H(x)=\sum^{n}_{i=0}\frac{2^{\binom{i}{2}}}{i!}\),那么

\[F(x)=G(x)*H'(x) \]

所以就把 \(H(x)\) 求逆再乘上 \(F(x)\) 即可。
时间复杂度 \(O(n\log n)\)。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=280010,MOD=1004535809,G=3,Ginv=334845270;
ll f[N],g[N],h[3][N],X[N],Y[N],fac[N],inv[N];
int n,rev[N];

ll fpow(ll x,ll k)
{
	ll ans=1;
	for (;k;k>>=1,x=x*x%MOD)
		if (k&1) ans=ans*x%MOD;
	return ans;
}

void NTT(ll *f,int tag,int lim)
{
	for (int i=0;i<lim;i++)
		if (i<rev[i]) swap(f[i],f[rev[i]]);
	for (int k=1;k<lim;k<<=1)
	{
		ll tmp=fpow((tag==1)?G:Ginv,(MOD-1)/(k<<1));
		for (int i=0;i<lim;i+=(k<<1))
		{
			ll w=1;
			for (int j=0;j<k;j++,w=w*tmp%MOD)
			{
				ll x=f[i+j],y=w*f[i+j+k]%MOD;
				f[i+j]=(x+y)%MOD; f[i+j+k]=(x-y+MOD)%MOD;
			}
		}
	}
}

void Fmul(ll *f,ll *g,int lim)
{
	memset(X,0,sizeof(X));
	memset(Y,0,sizeof(Y));
	for (int i=0;i<(lim>>1);i++)
		X[i]=f[i],Y[i]=g[i];
	NTT(X,1,lim); NTT(Y,1,lim);
	for (int i=0;i<lim;i++) X[i]=X[i]*Y[i]%MOD;
	NTT(X,-1,lim);
	ll inv=fpow(lim,MOD-2);
	for (int i=0;i<lim;i++) X[i]=X[i]*inv%MOD;
	memcpy(f,X,sizeof(X));
}

void Finv(ll *f,int n)
{
	h[0][0]=fpow(f[0],MOD-2);
	int id=0;
	for (int lim=4;lim<=n*4;lim<<=1)
	{
		id^=1;
		for (int i=0;i<lim;i++)
			rev[i]=(rev[i>>1]>>1)|((i&1)?(lim>>1):0);
		memcpy(h[2],h[id^1],sizeof(h[2]));
		Fmul(h[2],h[2],lim); Fmul(h[2],f,lim);
		for (int i=0;i<(lim>>1);i++)
			h[id][i]=((h[id^1][i]<<1)-h[2][i]+MOD)%MOD;
	}
	memcpy(f,h[id],sizeof(h[id]));
}

int main()
{
	scanf("%d",&n);
	fac[0]=inv[0]=1;
	for (int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
	inv[n]=fpow(fac[n],MOD-2);
	for (int i=n-1;i>=1;i--) inv[i]=inv[i+1]*(i+1)%MOD;
	for (int i=1;i<=n;i++) f[i]=fpow(2LL,1LL*i*(i-1)/2)*inv[i-1]%MOD;
	for (int i=0;i<=n;i++) g[i]=fpow(2LL,1LL*i*(i-1)/2)*inv[i]%MOD;
	Finv(g,n+1);
	int lim=1;
	while (lim<=2*n) lim<<=1;
	for (int i=0;i<lim;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)?(lim>>1):0);
	Fmul(f,g,lim);
	printf("%lld",f[n]*fac[n-1]%MOD);
	return 0;
}
上一篇:《实变函数简明教程》(邓东皋,常心怡编),第四章:Lebesgue积分,P115,第14题(利用Lebesgue控制收敛定理求函数列Lebesgue积分的极限)


下一篇:极值与凹凸性及简单例题