城市规划

一、题目

点此看题

二、解法

求无向联通图的个数是在是太难做了,我们考虑从无向图个数方面入手。

g ( i ) g(i) g(i)表示无向图点数为 i i i的总数,考虑每一条边的选与不选,方案数是 2 C ( n , 2 ) 2^{C(n,2)} 2C(n,2),设 f ( i ) f(i) f(i)为无向连通图的点数为 i i i的方案数,那么我们可以用 f f f来算 g g g,枚举一边联通,另外一边随便选:
g ( i ) = ∑ j = 1 i C i − 1 j − 1 ⋅ f ( j ) ⋅ g ( i − j ) g(i)=\sum_{j=1}^i C_{i-1}^{j-1}\cdot f(j)\cdot g(i-j) g(i)=j=1iCi1j1f(j)g(ij)为什么这里是 C n − 1 i − 1 C_{n-1}^{i-1} Cn1i1呢,因为我们要固定 1 1 1号点作为联通图的一个点,这样就可以保证相同形状的图只会被算一次。很容易看出来上面的柿子像卷积,再加上题目给的模数提示了这是 NTT \text{NTT} NTT的标准模数,我们考虑把组合拆开,重写这个柿子:
g ( i ) ( i − 1 ) ! = ∑ j = 1 i f ( j ) ( j − 1 ) ! ⋅ g ( i − j ) ( i − j ) ! \frac{g(i)}{(i-1)!}=\sum_{j=1}^i\frac{f(j)}{(j-1)!}\cdot\frac{g(i-j)}{(i-j)!} (i1)!g(i)=j=1i(j1)!f(j)(ij)!g(ij) G ( i ) = g ( i ) ( i − 1 ) ! G(i)=\frac{g(i)}{(i-1)!} G(i)=(i1)!g(i) H ( i ) = g ( i ) i ! H(i)=\frac{g(i)}{i!} H(i)=i!g(i) F ( i ) = f ( i ) ( i − 1 ) ! F(i)=\frac{f(i)}{(i-1)!} F(i)=(i1)!f(i),那么 F = G × H − 1 F=G\times H^{-1} F=G×H1,这里需要用到多项式求逆。

#include <cstdio>
#include <iostream>
#include <cmath>
#define int long long
using namespace std;
const int MAXN = 800005;
const int MOD = 1004535809;
int read()
{
    int num=0,flag=1;
    char c;
    while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
    while(c>='0'&&c<='9')num=(num<<3)+(num<<1)+(c^48),c=getchar();
    return num*flag;
}
int n,len,lg,Rev[MAXN],fac[MAXN],inv[MAXN];
int A[MAXN],B[MAXN],C[MAXN],F[MAXN],G[MAXN];
void init(int n)
{
    fac[0]=inv[0]=inv[1]=1;
    for(int i=2; i<=n; i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    for(int i=1; i<=n; i++) inv[i]=inv[i]*inv[i-1]%MOD;
    for(int i=1; i<=n; i++) fac[i]=fac[i-1]*i%MOD;
}
int qkpow(int a,int b)
{
    int res=1;
    while(b>0)
    {
        if(b&1) res=res*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return res;
}
void NTT(int *a,int len,int tmp)
{
    lg=(int)round(log2(len));
    for(int i=0; i<len; i++)
    {
        Rev[i]=(Rev[i>>1]>>1)|((i&1)<<(lg-1));
        if(i<Rev[i])
            swap(a[i],a[Rev[i]]);
    }
    for(int s=2; s<=len; s<<=1)
    {
        int t=s/2,w=(tmp==1)?qkpow(3,(MOD-1)/s):qkpow(3,(MOD-1)-(MOD-1)/s);
        for(int i=0; i<len; i+=s)
        {
            int x=1;
            for(int j=0; j<t; j++,x=x*w%MOD)
            {
                int fe=a[i+j],fo=a[i+j+t];
                a[i+j]=(fe+x*fo)%MOD;
                a[i+j+t]=((fe-fo*x)%MOD+MOD)%MOD;
            }
        }
    }
    if(tmp==1) return ;
    int inv=qkpow(len,MOD-2);
    for(int i=0; i<len; i++)
        a[i]=a[i]*inv%MOD;
}
void work(int n,int *a,int *b)
{
    len=1;
    while(len<=2*n) len<<=1;
    for(int i=0; i<len; i++) A[i]=B[i]=0;
    for(int i=0; i<n; i++) A[i]=a[i];
    for(int i=0; i<n; i++) B[i]=b[i];
    NTT(A,len,1);
    NTT(B,len,1);
    for(int i=0; i<len; i++) A[i]=2*B[i]-B[i]*B[i]%MOD*A[i],A[i]%=MOD;
    NTT(A,len,-1);
    for(int i=0; i<n; i++) b[i]=(A[i]%MOD+MOD)%MOD;
}
void get(int n,int *a,int *b)
{
    int cur=1;
    for(int i=0; i<n; i++) b[i]=0;
    b[0]=qkpow(a[0],MOD-2);
    while(cur<n)
    {
        cur<<=1;
        work(cur,a,b);
    }
}
signed main()
{
    n=read();
    init(n);
    F[0]=1;
    for(int i=1; i<=n; i++)
    {
        int tmp=qkpow(2,i*(i-1)/2%(MOD-1));
        G[i]=tmp*inv[i-1]%MOD;
        F[i]=tmp*inv[i]%MOD;
    }
    get(n+1,F,C);
    len=1;
    while(len<=2*n+2) len<<=1;
    NTT(G,len,1);
    NTT(C,len,1);
    for(int i=0; i<len; i++) G[i]=G[i]*C[i]%MOD;
    NTT(G,len,-1);
    printf("%lld\n",G[n]*fac[n-1]%MOD);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值