【洛谷P4841】城市规划
题目
题目链接:https://www.luogu.com.cn/problem/P4841
刚刚解决完电力网络的问题,阿狸又被领导的任务给难住了。
刚才说过,阿狸的国家有 \(n\) 个城市,现在国家需要在某些城市对之间建立一些贸易路线,使得整个国家的任意两个城市都直接或间接的连通。
为了省钱, 每两个城市之间最多只能有一条直接的贸易路径。对于两个建立路线的方案,如果存在一个城市对,在两个方案中是否建立路线不一样,那么这两个方案就是不同的,否则就是相同的。现在你需要求出一共有多少不同的方案。
好了,这就是困扰阿狸的问题。换句话说,你需要求出 \(n\) 个点的简单 (无重边无自环) 有标号无向连通图数目。
由于这个数字可能非常大, 你只需要输出方案数对 \(1004535809\) ( \(479 \times 2 ^{21} + 1\) ) 即可。
\(n\leq 130000\)。
思路
QuantAsk 和 my_dog 太强啦 orz。
设 \(f_i\) 表示 \(i\) 个点的无向连通图数量,\(g_i\) 表示 \(i\) 个点的无向图数量。
因为每一条边可选可不选,所以显然
考虑枚举点 \(1\) 所在联通块大小,不难得到
组合数拆开,移项
设 \(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!}\),那么
所以就把 \(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;
}