图的计数

一周一博客二专题计划

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

题面

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

看着就很典

思路

\(f(n)\)为n点连通图数目。设\(g(n)\)为n点不一定联通图数目,显然直接枚举每条边是否存在,\(g(n)=2^{\frac{n*(n-1)}{2}}\)

\[g(n)=\sum_{i=1}^{n} \left( \begin{array}{c} n-1 \\ i-1 \end{array} \right) f(i)g(n-i) \]

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

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

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

\[\frac{g(n)}{(n-1)!}=\sum_{i=1}^{n} \frac{f(i)}{(i-1)!} \frac{g(n-i)}{(n-i)!} \]

很明显,设

\[a_i=\frac{f(i+1)}{i!} \]

\[b_j=\frac{2^{\frac{j*(j-1)}{2}}}{j!} \]

\[c_k=\frac{2^{\frac{k*(k+1)}{2}}}{k!} \]

\[c_k=\sum_{i+j=k}a_ib_j \]

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

答案\(f(n)=a_{n-1}*(n-1)!\)

代码


#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^{\frac{(n-1)(n-2)}{2}} \sum_{i=0}^{n-1} \left( \begin{array}{} n-1 \\ i \end{array} \right) i^k\)

先忽略\(n2^{\frac{(n-1)(n-2)}{2}}\)。考虑将太大的n化掉,注意到n出现在组合数中,而组合数有求和公式。但是 \(i^k\) 使得无法使用求和公式,要把它化掉,而我们有斯特林数 \(i^k = \sum_{j=0}^{k} \left(\begin{array}{} i \\ j \end{array} \right) \begin{Bmatrix} k \\ j \end{Bmatrix} j!\)。将这个式子带入,再把组合数全部化开

\[\begin{aligned} & \sum_{i=0}^{n-1} \frac{(n-1)!}{i!(n-i-1)!} \sum_{j=0}^{k} \frac{i!}{j!(i-j)!} \begin{Bmatrix} k \\ j \end{Bmatrix} j! \\ &= (n-1)!\sum_{i=0}^{n-1} \frac{1}{(n-i-1)!} \sum_{j=0}^{k} \frac{1}{(i-j)!} \begin{Bmatrix} k \\ j \end{Bmatrix} \\ &= (n-1)!\sum_{j=0}^{k} \begin{Bmatrix} k \\ j \end{Bmatrix} \sum_{i=0}^{n-1} \frac{1}{(n-i-1)!(i-j)!} \\ &= (n-1)!\sum_{j=0}^{k} \begin{Bmatrix} k \\ j \end{Bmatrix} \sum_{i=0}^{n-1} \left(\begin{array}{} n-j-1 \\ n-i-1 \end{array} \right) \frac{1}{(n-j-1)!} \\ &= \sum_{j=0}^{k} \begin{Bmatrix} k \\ j \end{Bmatrix} \frac{2^{n-j-1}(n-1)!}{(n-j-1)!} \end{aligned}\]

这样只需用NTT算出一行的第二类斯特林数\(\begin{Bmatrix} k \\ j \end{Bmatrix}= \sum_{i=0}^{j} \frac{(-1)^i}{i!} \frac{(j-i)^k}{(j-i)!}\)

阶乘其实是k次下降幂

\(O(n)\)求和,再把之前忽略的$n2^{\frac{(n-1)(n-2)}{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。只要\(d_i \in [0,n-2]\)\(\sum di =n-2\),d序列就合法。容易想到di贡献为多项式di次项的系数,n个多项式连乘的第n-2项系数与答案有关

对于序列d的贡献

\[\begin{aligned} &\frac{(n-2)!}{\prod d_i !} \prod a_i^{d_i+1} (d_i+1)^m \sum (d_i+1)^m \\ =& (n-2)!\prod a_i \frac{1}{\prod d_i !} \prod a_i^{d_i} (d_i+1)^m \sum (d_i+1)^m \\ =& (n-2)!\prod a_i \sum_j \big( \prod \frac{1}{d_i !} a_i^{d_i} \big) \big( \prod _{i \neq j}(d_i+1)^m\big) \big( (d_j+1)^{2m} \big) \\ \end{aligned}\]

前面的\((n-2)!\prod a_i\)是常数,不管。关于后面的\(\sum\)\(\sum d_i\)的生成函数\(F(x)\)\(F(x)\)是关于\(a_ix\)的多项式的乘积

\[\begin{aligned}A(x)&=\sum_{k=0}\frac{1}{k!}(k+1)^mx^k \\ B(x)&=\sum_{k=0}\frac{1}{k!}(k+1)^{2m}x^k \\ F(x)&=\sum_i B(a_ix) \prod_{j\neq i} A(a_jx) \\ &=\big(\sum_i \frac{B(a_ix)}{A(a_ix)} \big) \big(\prod_i A(a_ix) \big) \\ &=\big(\sum_i \frac{B(a_ix)}{A(a_ix)} \big) \Big(\exp \big(\sum_i \ln A(a_ix) \big) \Big) \end{aligned}\]

算出\(\frac{B(x)}{A(x)}\)\(\ln A(x)\)后,t次项系数乘上\(\sum_i {a_i}^t\),可以\(O(n^2)\)预处理

点击查看代码
#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 @ 2024-01-10 15:57  yisiwunian  阅读(20)  评论(0编辑  收藏  举报