图的计数

一周一博客二专题计划

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

题面

n 个点的简单 (无重边无自环) 有标号无向连通图数目。

看着就很典

思路

f(n)为n点连通图数目。设g(n)为n点不一定联通图数目,显然直接枚举每条边是否存在,g(n)=2n(n1)2

g(n)=i=1n(n1i1)f(i)g(ni)

可看作枚举1号节点所在连通块的大小,组合数是从其他n-1个点中选出与1同联通块的点

很多博客都是推完式子然后发现卷积形式。其实应该看到多项式相乘,考虑卷积求解,化式子时尽量将i相关放在一起,n-i相关放在一起

而组合数上的n-1明显要拆出来

g(n)(n1)!=i=1nf(i)(i1)!g(ni)(ni)!

很明显,设

ai=f(i+1)i!

bj=2j(j1)2j!

ck=2k(k+1)2k!

ck=i+j=kaibj

NTT的式子已经出来了,不同的是此时我们已知c而想求a,直接对b多项式求逆a=cinvb

答案f(n)=an1(n1)!

代码


#include<bits/stdc++.h>
using namespace std;
const int MAX=2.7e5+10;
#define int long long
const int mod=1004535809;
int n,bl,bc,rev[MAX],a[MAX],b[MAX],f[MAX],g[MAX];
int fac[MAX],inv[MAX],c[MAX];
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();}
	return x*f;
}
inline int power(int a,int b){
    int res=1;
    while(b){
        if(b&1)  res=res*a%mod;
        a=a*a%mod;b>>=1;
    }return res;
}inline void NTT(int*,int,int);
void solve(int);
inline void work(int len){
    bl=1;bc=0;
    while(bl<=len)  bl<<=1,++bc;
    for(int i=0;i<bl;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<bc-1);
}
signed main(){ 
    n=read();fac[0]=1;
    for(int i=1;i<=n;++i)  fac[i]=fac[i-1]*i%mod;
    inv[n]=power(fac[n],mod-2);
    for(int i=n-1;i>=0;--i)  inv[i]=inv[i+1]*(i+1)%mod;
    f[0]=1;c[0]=1;
    for(int i=1;i<n;++i){
        f[i]=power(2,(i-1)*i/2)*inv[i]%mod;
        c[i]=power(2,i*(i+1)/2)*inv[i]%mod;
    }
    solve(n);work(n<<1);
    NTT(g,bl,1);NTT(c,bl,1);
    for(int i=0;i<bl;++i)  g[i]=g[i]*c[i]%mod;
    NTT(g,bl,-1);printf("%d",g[n-1]*fac[n-1]%mod);
}
void solve(int len){
    if(len==1){g[0]=power(f[0],mod-2);return;}
    solve(len+1>>1);work(len+n);
    memcpy(a,f,sizeof(a));memset(b,0,sizeof(b));
    for(int i=0;i<len+1>>1;++i)  b[i]=g[i];
    NTT(a,bl,1);NTT(b,bl,1);
    for(int i=0;i<bl;++i)  g[i]=b[i]*((2-a[i]*b[i]%mod+mod)%mod)%mod;
    NTT(g,bl,-1);
}inline void NTT(int *a,int n,int op){
    for(int i=0;i<n;++i)
        if(i<rev[i])  swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1){
        int wn=power(3,(op*(mod-1)/(i<<1)+mod-1)%(mod-1));
        for(int j=0;j<n;j+=i<<1){
            int w=1;
            for(int k=0;k<i;++k){
                int x=a[j+k],y=a[j+k+i]*w%mod;
                a[j+k]=(x+y)%mod;a[j+k+i]=(x-y+mod)%mod;
                w=w*wn%mod;
            }
        }
    }if(op==-1){
        int inv=power(n,mod-2);
        for(int i=0;i<n;++i)  a[i]=a[i]*inv%mod;
    }
}

bzoj5093: 图的价值

因为上周没写,并且一道题太短了,所以又加了一道题

无论是将对边的讨论转化到对单点的讨论,还是将组合数拆开再合起来都非常妙

题面

“简单无向图”是指无重边、无自环的无向图(不一定连通)。 一个带标号的图的价值定义为每个点度数的k次方的和。 给定n和k,请计算所有n个点的带标号的简单无向图的价值之和。 因为答案很大,请对998244353取模输出。

思路

因为每个点对于答案的贡献都是相同的(点1可以出现的情形,点2可以完全复刻),于是只讨论单点的度数,最后再乘上一个n。由此我们不用关注点点之间的关系。

点1从n-1个点中选出i个连了i条边,其他点之间随意,答案为n2(n1)(n2)2i=0n1(n1i)ik

先忽略n2(n1)(n2)2。考虑将太大的n化掉,注意到n出现在组合数中,而组合数有求和公式。但是 ik 使得无法使用求和公式,要把它化掉,而我们有斯特林数 ik=j=0k(ij){kj}j!。将这个式子带入,再把组合数全部化开

i=0n1(n1)!i!(ni1)!j=0ki!j!(ij)!{kj}j!=(n1)!i=0n11(ni1)!j=0k1(ij)!{kj}=(n1)!j=0k{kj}i=0n11(ni1)!(ij)!=(n1)!j=0k{kj}i=0n1(nj1ni1)1(nj1)!=j=0k{kj}2nj1(n1)!(nj1)!

这样只需用NTT算出一行的第二类斯特林数{kj}=i=0j(1)ii!(ji)k(ji)!

阶乘其实是k次下降幂

O(n)求和,再把之前忽略的n2(n1)(n2)2乘上就是最后答案

代码

#include<bits/stdc++.h>
using namespace std;
const int MAX=5.7e5+10;
#define int long long
const int mod=998244353;
int n,k,f[MAX],a[MAX],b[MAX],bl=1,bc,rev[MAX],c[MAX],ans;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();}
	return x*f;
}
inline int power(int a,int b){
    int res=1;
    while(b){
        if(b&1)  res=res*a%mod;
        a=a*a%mod;b>>=1;
    }return res;
}inline void NTT(int*,int,int);
signed main(){ 
    n=read();k=read();f[0]=1;
    for(int i=1;i<=k;++i)  f[i]=f[i-1]*i%mod;
    while(bl<=k<<1)  bl<<=1,++bc;
    for(int i=0;i<bl;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<bc-1);
    for(int i=0,p=1;i<=k;++i,p*=-1){
        a[i]=(p*power(f[i],mod-2)+mod)%mod;
        b[i]=power(i,k)*power(f[i],mod-2)%mod;
    }NTT(a,bl,1);NTT(b,bl,1);
    for(int i=0;i<bl;++i)  c[i]=a[i]*b[i];
    NTT(c,bl,-1);
    for(int i=0,p=1;i<=k;++i){
        p=p*(n-i)%mod;
        ans=(ans+c[i]*p%mod*power(2,n-i-1)%mod)%mod;
    }  
    ans=ans*power(2,(n-1)*(n-2)/2)%mod;
    printf("%lld",ans);
}inline void NTT(int *a,int n,int op){
    for(int i=0;i<n;++i)
        if(i<rev[i])  swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1){
        int wn=power(3,(op*(mod-1)/(i<<1)+mod-1)%(mod-1));
        for(int j=0;j<n;j+=i<<1){
            int w=1;
            for(int k=0;k<i;++k){
                int x=a[j+k],y=a[j+k+i]*w%mod;
                a[j+k]=(x+y)%mod;a[j+k+i]=(x-y+mod)%mod;
                w=w*wn%mod;
            }
        }
    }if(op==-1){
        int inv=power(n,mod-2);
        for(int i=0;i<n;++i)  a[i]=a[i]*inv%mod;
    }
}

[清华集训2017] 生成树计数

众所周知,树也是图

首先将树对应到purfer序列,设di为i点在序列中出现次数,即i点度数-1。只要di[0,n2]di=n2,d序列就合法。容易想到di贡献为多项式di次项的系数,n个多项式连乘的第n-2项系数与答案有关

对于序列d的贡献

(n2)!di!aidi+1(di+1)m(di+1)m=(n2)!ai1di!aidi(di+1)m(di+1)m=(n2)!aij(1di!aidi)(ij(di+1)m)((dj+1)2m)

前面的(n2)!ai是常数,不管。关于后面的di的生成函数F(x)F(x)是关于aix的多项式的乘积

A(x)=k=01k!(k+1)mxkB(x)=k=01k!(k+1)2mxkF(x)=iB(aix)jiA(ajx)=(iB(aix)A(aix))(iA(aix))=(iB(aix)A(aix))(exp(ilnA(aix)))

算出B(x)A(x)lnA(x)后,t次项系数乘上iait,可以O(n2)预处理

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=4e5+10;
#define int long long
const int mod=998244353;
int n,m=1,bl,bc,a[MAX],b[MAX],rev[MAX],c[MAX],d[MAX],A[MAX],B[MAX],g2[MAX],ans[MAX];
int lna[MAX],inva[MAX],ea[MAX],ANS=1;
inline int read(){
	int x=0,f=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^'0');c=getchar();}
	return x;
}
inline int power(int a,int b){
    int res=1;
    while(b){
        if(b&1)  res=res*a%mod;
        a=a*a%mod;b>>=1;
    }return res;
}inline void NTT(int*,int,int);
void solve(int*,int*,int);
inline void dao(int *g,int *f,int n){
    for(int i=0;i<n;++i)  g[i]=f[i+1]*(i+1)%mod;g[n-1]=0;
}inline void jifen(int *g,int n){
    for(int i=n;i;--i)  g[i]=g[i-1]*power(i,mod-2)%mod;
    g[0]=0;
}inline void mul(int*,int*,int*,int);
inline void ln(int *f,int *g,int n){
    int m=1;
    while(m<=n)  m<<=1;
    for(int i=0;i<m;++i)  g[i]=0;//
    solve(f,g,m);dao(g2,f,n);
    mul(g,g2,g,n);
    jifen(g,n);
}void exp(int *a,int *b,int n){
    if(n==1){b[0]=1;return;}
    exp(a,b,n+1>>1);
    ln(b,c,n);
    for(int i=0;i<n;++i)  d[i]=(a[i]-c[i]+mod)%mod;
    d[0]++;
    mul(b,d,b,n);
    for(int i=n;i<bl;++i)  b[i]=0;
}
signed main(){ 
    // freopen("1.txt","r",stdin);
	n=read();m=read();
	for(int i=1;i<=n;++i)  A[i]=read(),B[i]=1,ANS=ANS*A[i]%mod;
	for(int i=0;i<n-1;++i){
		for(int j=1;j<=n;++j)  ans[i]+=B[j],B[j]=B[j]*A[j]%mod;
		ans[i]%=mod;
	}
    for(int i=0,p=1;i<n-1;++i){
        int x=power(i+1,m);
        a[i]=p*x%mod;b[i]=a[i]*x%mod;p=p*power(i+1,mod-2)%mod;\
    }
    ln(a,lna,n-1);
    for(int i=0;i<n-1;++i)  lna[i]=lna[i]*ans[i]%mod;
    exp(lna,ea,n-1);
    int m=1;
    while(m<=n)  m<<=1;
    solve(a,inva,m);
    mul(b,inva,b,n-1);
    for(int i=0;i<n-1;++i)  b[i]=b[i]*ans[i]%mod;
    mul(b,ea,b,n-1);
    ANS=ANS*b[n-2]%mod;
    for(int i=1;i<=n-2;++i)  ANS=ANS*i%mod;
    printf("%lld",ANS);
}
void solve(int *f,int *g,int len){
    if(len==1){g[0]=power(f[0],mod-2);return;}
    solve(f,g,len>>1);
    bl=1;bc=0;
    while(bl<=len)  bl<<=1,++bc;
    for(int i=0;i<bl;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<bc-1);
	for(int i=0;i<len;i++) A[i]=f[i],B[i]=g[i];
	for(int i=len;i<bl;i++) A[i]=B[i]=0; NTT(A,bl,1);NTT(B,bl,1);
	for(int i=0;i<bl;i++) A[i]=1ll*A[i]*B[i]%mod*B[i]%mod; NTT(A,bl,-1);
	for(int i=0;i<len;i++) g[i]=((2ll*g[i]%mod-A[i])%mod+mod)%mod;
}inline void NTT(int *a,int n,int op){
    for(int i=0;i<n;++i)
        if(i<rev[i])  swap(a[i],a[rev[i]]);
    for(int i=1;i<n;i<<=1){
        int wn=power(3,(op*(mod-1)/(i<<1)+mod-1)%(mod-1));
        for(int j=0;j<n;j+=i<<1){
            int w=1;
            for(int k=0;k<i;++k){
                int x=a[j+k],y=a[j+k+i]*w%mod;
                a[j+k]=(x+y)%mod;a[j+k+i]=(x-y+mod)%mod;
                w=w*wn%mod;
            }
        }
    }if(op==-1){
        int inv=power(n,mod-2);
        for(int i=0;i<n;++i)  a[i]=a[i]*inv%mod;
    }
}inline void mul(int *a,int *b,int *c,int n){
    bl=1;bc=0;
    while(bl<=n<<1)  bl<<=1,++bc;
    for(int i=0;i<bl;++i)
        rev[i]=(rev[i>>1]>>1)|((i&1)<<bc-1);
    for(int i=0;i<n;++i)  A[i]=a[i],B[i]=b[i];
    for(int i=n;i<bl;++i)  A[i]=B[i]=0;
    NTT(A,bl,1);NTT(B,bl,1);
    for(int i=0;i<bl;++i)  c[i]=A[i]*B[i]%mod;
    NTT(c,bl,-1);
}


posted @   yisiwunian  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示