洛谷P4841 [集训队作业2013]城市规划

题目描述

刚刚解决完电力网络的问题,阿狸又被领导的任务给难住了。

刚才说过,阿狸的国家有 n n n 个城市,现在国家需要在某些城市对之间建立一些贸易路线,使得整个国家的任意两个城市都直接或间接的连通。

为了省钱, 每两个城市之间最多只能有一条直接的贸易路径。对于两个建立路线的方案,如果存在一个城市对,在两个方案中是否建立路线不一样,那么这两个方案就是不同的,否则就是相同的。现在你需要求出一共有多少不同的方案。

好了,这就是困扰阿狸的问题。换句话说,你需要求出 n n n 个点的简单 (无重边无自环) 有标号无向连通图数目。

由于这个数字可能非常大, 你只需要输出方案数对 1004535809 1004535809 1004535809 ( 479 × 2 21 + 1 479 \times 2 ^{21} + 1 479×221+1 ) 即可。

1 ≤ n ≤ 130000 1\le n\le130000 1n130000

题解

大力推式子

g ( n ) g(n) g(n) n n n 个点的简单有标号无向图数目。

相当于有 n ( n − 1 ) 2 \dfrac{n(n-1)}2 2n(n1) 条边,选或不选,可得 g ( n ) = 2 n ( n − 1 ) 2 g(n)=2^{\frac{n(n-1)}2} g(n)=22n(n1)

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

想要求连通图数目,可以用所有的减去不连通的。

如何求不连通图的数目呢?

我们可以递归考虑。首先确定一个顺序,把第一个点固定。由于不连通图至少有两个连通块,所以枚举包括第一个点的连通块点的数量 i i i,范围为 1 ∼ n − 1 1\sim n-1 1n1,其他的点随意选,还有要在 ( n − 1 ) (n-1) (n1) 个点选 ( i − 1 ) (i-1) (i1) 个(第一个点固定)。方案如下。

f ( n ) = g ( n ) − ∑ i = 1 n − 1 f ( i ) g ( n − i ) ( n − 1 i − 1 ) f(n)=g(n)-\sum\limits_{i=1}^{n-1}f(i)g(n-i)\dbinom{n-1}{i-1} f(n)=g(n)i=1n1f(i)g(ni)(i1n1)

把组合数拆开得
f ( n ) = g ( n ) − ( n − 1 ) ! ∑ i = 1 n − 1 f ( i ) ( i − 1 ) ! g ( n − i ) ( n − i ) ! f(n)=g(n)-(n-1)!\sum\limits_{i=1}^{n-1}\dfrac{f(i)}{(i-1)!}\dfrac{g(n-i)}{(n-i)!} f(n)=g(n)(n1)!i=1n1(i1)!f(i)(ni)!g(ni)

观察到式子后半部分类似于卷积的形式,想办法把它构造成标准形式,就是把 i i i 的枚举范围变成 0 ∼ n 0\sim n 0n ( i − 1 ) ! (i-1)! (i1)! 转换为 i ! i! i!

i = 0 i=0 i=0 时, ( i − 1 ) ! (i-1)! (i1)! 无意义,所以可以把它变成 i ! i \dfrac{i!}{i} ii!,这样结果就是 0 0 0 了,不会造成影响。

i = n i=n i=n 是,卷积为 f ( n ) ( n − 1 ) ! \dfrac{f(n)}{(n-1)!} (n1)!f(n)

因此
g ( n ) ( n − 1 ) ! = ∑ i = 0 n f ( i ) i i ! g ( n − i ) ( n − i ) ! \dfrac{g(n)}{(n-1)!}=\sum\limits_{i=0}^{n}\dfrac{f(i)i}{i!}\dfrac{g(n-i)}{(n-i)!} (n1)!g(n)=i=0ni!f(i)i(ni)!g(ni)

根据套路,构造 F ( x ) = ∑ i = 0 ∞ f ( i ) i i ! x i , G ( x ) = ∑ i = 0 ∞ g ( i ) i ! x i F(x)=\sum\limits_{i=0}^{\infty}\dfrac{f(i)i}{i!}x^i,G(x)=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{i!}x^i F(x)=i=0i!f(i)ixi,G(x)=i=0i!g(i)xi


F ( x ) ⋅ G ( x ) = ∑ i = 0 ∞ x i ∑ j + k = i f ( j ) j j ! g ( k ) ( k ) ! = ∑ i = 0 ∞ g ( i ) ( i − 1 ) ! x i \begin{aligned} F(x)\cdot G(x)&=\sum\limits_{i=0}^{\infty}x^i\sum\limits_{j+k=i}\dfrac{f(j)j}{j!}\dfrac{g(k)}{(k)!}\\ &=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{(i-1)!}x^i \end{aligned} F(x)G(x)=i=0xij+k=ij!f(j)j(k)!g(k)=i=0(i1)!g(i)xi

H ( x ) = ∑ i = 0 ∞ g ( i ) ( i − 1 ) ! x i H(x)=\sum\limits_{i=0}^{\infty}\dfrac{g(i)}{(i-1)!}x^i H(x)=i=0(i1)!g(i)xi

可得 F ( x ) ⋅ G ( x ) = H ( x ) F(x)\cdot G(x)=H(x) F(x)G(x)=H(x)

两边同时模 x n + 1 x^{n+1} xn+1

得到 F ( x ) ⋅ G ( x ) ≡ H ( x ) ( m o d x n + 1 ) F(x)\cdot G(x)\equiv H(x)\pmod{x^{n+1}} F(x)G(x)H(x)(modxn+1)

很容易就得到 F ( x ) ≡ H ( x ) G ( x ) ( m o d x n + 1 ) F(x)\equiv\dfrac{H(x)}{G(x)}\pmod{x^{n+1}} F(x)G(x)H(x)(modxn+1)

H ( x ) , G ( x ) H(x),G(x) H(x),G(x) 都是已知的,使用多项式求逆,多项式乘法就可以求出 F ( x ) F(x) F(x)

答案即为 F ( x ) F(x) F(x) 次数为 n n n 的项的系数乘 ( i − 1 ) ! (i-1)! (i1)!

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=(1<<18)+1;
const ll mod=1004535809,g=3;
int len=1,n;
ll w,wn,G1[N],G[N],invG[N],f[N],a1[N],inv[N];
ll ksm(ll a,ll b)
{
    ll ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
void change(ll num[])
{
    for(int i=1,j=len/2;i<len-1;i++){
        if(i<j) swap(num[i],num[j]);
        int k=len/2;
        while(j>=k) j-=k,k>>=1;
        if(j<k) j+=k;
    }
}
void ntt(ll num[],int fl)
{
    for(int i=2;i<=len;i<<=1){
        if(fl==1) wn=ksm(g,(mod-1)/i);
        else wn=ksm(g,mod-1-(mod-1)/i);
        for(int j=0;j<len;j+=i){
            w=1;
            for(int k=j;k<j+i/2;k++){
                ll u=w*num[k+i/2]%mod,t=num[k];
                num[k]=(t+u)%mod;
                num[k+i/2]=(t-u+mod)%mod;
                w=w*wn%mod;
            }
        }
    }
    if(fl==-1){
        ll inv=ksm(len,mod-2);
        for(int i=0;i<len;i++) num[i]=num[i]*inv%mod;
    }
}
int read()
{
    int sum=0,c=getchar();
    while(c<48||c>57) c=getchar();
    while(c>=48&&c<=57) sum=sum*10+c-48,c=getchar();
    return sum;
}
void solve(int n,ll a[],ll ans[])
{
	if(n==1){ans[0]=ksm(a[0],mod-2);return;}
	solve((n+1)/2,a,ans);
	len=1;
	while(len<2*n) len*=2;
	for(int i=0;i<n;i++) a1[i]=a[i];
	for(int i=n;i<len;i++) a1[i]=0;
	change(a1),change(ans);
	ntt(a1,1),ntt(ans,1);
	for(int i=0;i<len;i++) ans[i]=ans[i]*(2-ans[i]*a1[i]%mod+mod)%mod;
	change(ans),ntt(ans,-1);
	for(int i=n;i<len;i++) ans[i]=0;
}
int main()
{
    scanf("%d",&n);
    f[0]=1,G1[0]=0,G[0]=1;
    for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
    inv[n]=ksm(f[n],mod-2);
    for(int i=n-1;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
    for(int i=1;i<=n;i++) G1[i]=ksm(2,1ll*i*(i-1)/2),G[i]=G1[i]*inv[i]%mod,G1[i]=G1[i]*i%mod*inv[i]%mod;
    solve(n+1,G,invG);
    len=1;
    while(len<(n+1)*2) len*=2;
    change(G1),ntt(G1,1);
    change(invG),ntt(invG,1);
    for(int i=0;i<len;i++) G1[i]=G1[i]*invG[i]%mod;
    change(G1),ntt(G1,-1);
    printf("%lld\n",G1[n]*f[n-1]%mod);
}
  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值