[BZOJ3456]城市规划(组合数学+容斥原理+NTT+多项式求逆)

35 篇文章 0 订阅
23 篇文章 0 订阅

题目描述

传送门

题目大意:求n个点的无重边无自环无向连通图数目。

题解

这题好强啊。。

f(i) 表示与1连通的连通块大小为i(包括1)的连通图数目
如果要是将i个点之间的 2i(i1)2 条边随便连的话会有一些不合法的方案,即有一些点和i不连通,所以这里要容斥一下

f(i)=2i(i1)2j=0i1Cj1i1f(j)2(ij)(ij1)2

这个式子的意义是先随便连边,然后除去不合法方案;对于不合法方案,先从剩余的i-1个点里选出j-1个点,然后强制选出来的这j个点和1连通,剩余的点和1不连通,它们之间随便连边
然后移项之后将组合数展开化简
f(i)=2i(i1)2j=0i1Cj1i1f(j)2(ij)(ij1)2

j=0iCj1i1f(j)2(ij)(ij1)2=2i(i1)2

j=0i(i1)!(j1)!(ij)!f(j)2(ij)(ij1)2=2i(i1)2

j=0if(j)(j1)!2(ij)(ij1)2(ij)!=2i(i1)2(i1)!

A(i)=f(i)(i1)!B(i)=2i(i1)2i!C(i)=2i(i1)2(i1)!
那么就可以将这个式子化成一个卷积的形式了
C(i)=j=0iA(j)B(ij)

现在我们可以预处理出来 B C的每一项,那么根据之前求出的 C=A×B ,我们只需要求出来 B 在模xn意义下的逆 B1 ,那么 A=C×B1 ,最终 A(n)(n1)! 就是答案

代码

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define LL long long
#define Mod 1004535809
#define N 600005

int lim,m,n,L,R[N];
LL mul[N],inv[N],mulinv[N];
LL A[N],B[N],C[N],invB[N],c[N];

void init()
{
    mul[0]=1LL;
    for (int i=1;i<=lim;++i) mul[i]=mul[i-1]*(LL)i%Mod;
    inv[1]=1LL;
    for (int i=2;i<=n<<1;++i) inv[i]=inv[Mod%i]*(Mod-Mod/i)%Mod;
    mulinv[0]=1LL;
    for (int i=1;i<=lim;++i) mulinv[i]=mulinv[i-1]*inv[i]%Mod;
}
LL fast_pow(LL a,LL p)
{
    LL ans=1LL;
    for (;p;p>>=1LL,a=a*a%Mod)
        if (p&1LL)
            ans=ans*a%Mod;
    return ans;
}
void NTT(LL a[N],int n,int opt)
{
    L=0;for (int i=1;i<n;i<<=1) ++L;
    for (int i=0;i<n;++i)
        R[i]=(R[i>>1]>>1)|((i&1)<<(L-1));
    for (int i=0;i<n;++i)
        if (i<R[i]) swap(a[i],a[R[i]]);
    for (int k=1;k<n;k<<=1)
    {
        LL wn=fast_pow(3,(Mod-1)/(k<<1));
        for (int i=0;i<n;i+=(k<<1))
        {
            LL w=1LL;
            for (int j=0;j<k;++j,w=w*wn%Mod)
            {
                LL x=a[i+j],y=w*a[i+j+k]%Mod;
                a[i+j]=(x+y)%Mod;a[i+j+k]=(x-y+Mod)%Mod;
            }
        }
    }
    if (opt==-1) reverse(a+1,a+n);
}
void inverse(int n,LL a[N],LL b[N],LL c[N])
{
    if (n==1) b[0]=fast_pow(a[0],Mod-2);
    else
    {
        inverse(n>>1,a,b,c);
        int k=n<<1;
        for (int i=0;i<n;++i) c[i]=a[i];
        for (int i=n;i<k;++i) c[i]=0;
        NTT(c,k,1);NTT(b,k,1);
        for (int i=0;i<k;++i) b[i]=(2-c[i]*b[i]%Mod+Mod)%Mod*b[i]%Mod;
        NTT(b,k,-1);
        for (int i=0;i<n;++i) b[i]=b[i]*inv[k];
        for (int i=n;i<k;++i) b[i]=0;
    }
}
int main()
{
    scanf("%d",&lim);
    m=lim<<1;
    for (n=1;n<=m;n<<=1);
    init();
    B[0]=1LL;C[0]=0LL;
    for (int i=1;i<=lim;++i)
    {
        LL mi=fast_pow(2,(LL)i*(i-1)/2);
        B[i]=mi*mulinv[i]%Mod;C[i]=mi*mulinv[i-1]%Mod;
    }
    inverse(n,B,invB,c);
    NTT(invB,n,1);NTT(C,n,1);
    for (int i=0;i<=n;++i) A[i]=invB[i]*C[i]%Mod;
    NTT(A,n,-1);
    for (int i=0;i<=n;++i) A[i]=A[i]*inv[n]%Mod;
    printf("%lld\n",A[lim]*mul[lim-1]%Mod);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值