图的计数
一周一博客二专题计划
[集训队作业2013] 城市规划
题面
n 个点的简单 (无重边无自环) 有标号无向连通图数目。
看着就很典
思路
设\(f(n)\)为n点连通图数目。设\(g(n)\)为n点不一定联通图数目,显然直接枚举每条边是否存在,\(g(n)=2^{\frac{n*(n-1)}{2}}\)
可看作枚举1号节点所在连通块的大小,组合数是从其他n-1个点中选出与1同联通块的点
很多博客都是推完式子然后发现卷积形式。其实应该看到多项式相乘,考虑卷积求解,化式子时尽量将i相关放在一起,n-i相关放在一起
而组合数上的n-1明显要拆出来
很明显,设
有
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!\)。将这个式子带入,再把组合数全部化开
这样只需用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的贡献
前面的\((n-2)!\prod a_i\)是常数,不管。关于后面的\(\sum\)设\(\sum d_i\)的生成函数\(F(x)\),\(F(x)\)是关于\(a_ix\)的多项式的乘积
算出\(\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);
}