HDU5824 Graph 题解
题意
\(~~~~\) 定义一个图的权值是它的极大联通子图中的树的个数的 \(k\) 次方。计算所有有标号生成图的权值和 \(\bmod 998244353\)。
\(~~~~\) \(1\leq n\leq 10000,1\leq k\leq 20\)。
题解
本文版权归 Azazel 与博客园共有,欢迎转载,但需保留此声明,并给出原文地址,谢谢合作。
原文地址:https://www.cnblogs.com/Azazel/p/16471299.html
\(~~~~\) 万物皆可生成函数。
\(~~~~\) 记 \(f(G)\) 表示图 \(G\) 的极大联通子图中树的数量,先不用管它怎么算或者怎么着,先理解好这个定义就行。
\(~~~~\) 那么我们的答案就可以被写作\(\sum_{G} f^i(G)\) ,然后化成 \(\sum_{i=1}^k \begin{Bmatrix} k\\i \end{Bmatrix}\times \sum_G f^{\underline i}(G)\) ,而这么写的 motivation 是看到 \(k\) 很小可以放肆拆开。(\(\text{trick1:}\) 看到很小的幂次不妨拆成第二类斯特林数乘下降幂的形式)
\(~~~~\) 然后呢?然后组合意义天地灭,考虑后面那个求和,它实际上表示从 \(G\) 中有序地选出 \(i\) 棵树的方案数。那反过来我们也可以说成是至少包含这 \(i\) 棵树的图的个数。 (这真的是碳基生物能想出来的组合意义吗)
\(~~~~\) 所以 \(G\) 就被我们拆成了没那么复杂的两部分:图、树。那我们就可以顺势改写 \(f\) :\(\sum_{G}f^{\underline i}(G)\) 即为 \(T^i(x)\times G(x)\) ,其中 \(T,G\) 表示包含 \(x\) 个点的树或图的生成函数。其中 \(T_0=0,G_0=1\) ,那我们就可以写出 \(T\) 和 \(G\) 的 EGF:
\(~~~~\) 这两个东西做 \(T^{i}(x) \times G(x)\) 那就是上面的后一个求和号,然后再乘上斯特林数就是答案。同时注意有标号图的顺序问题,要再把顺序乘回去。
代码
\(~~~~\) 一定要搞清楚换完字母过后怎么填,不然就会像我一样一个字母打错调一个小时。
#include <cstdio>
#include <algorithm>
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
x*=f;
}
const int MOD=998244353;
int N,Lg,To[400005];
int Add(int a,int b){return (a+b)%MOD;}
int Dec(int a,int b){return (a-b+MOD)%MOD;}
int Mul(int a,int b){return 1ll*a*b%MOD;}
int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
const int g=3,gi=qpow(3,MOD-2);
void NTT(int *S,int op)
{
for(int i=0;i<N;i++) if(i<To[i]) swap(S[i],S[To[i]]);
for(int i=1;i<N;i<<=1)
{
int W=qpow((op==1)?g:gi,(MOD-1)/(i<<1));
for(int j=0;j<N;j+=i<<1)
{
int w=1;
for(int k=0;k<i;k++,w=Mul(w,W))
{
int x=S[j+k],y=Mul(S[i+j+k],w);
S[j+k]=Add(x,y); S[i+j+k]=Dec(x,y);
}
}
}
if(op==-1)
{
int Inv=qpow(N,MOD-2);
for(int i=0;i<N;i++) S[i]=Mul(S[i],Inv);
}
}
int S[25][25];
int T[400005],Fac[400005],Inv[400005];
void Init()
{
Fac[0]=1;
for(int i=1;i<=10000;i++) Fac[i]=Mul(Fac[i-1],i);
Inv[10000]=qpow(Fac[10000],MOD-2);
for(int i=9999;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
S[0][1]=1;
for(int i=1;i<=20;i++)
for(int j=1;j<=i;j++) S[i][j]=Add(S[i-1][j-1],Mul(S[i-1][j],j));
}
int Tmp[25][400005],Tmp2[400005],tp[400005];
struct Ask{
int n,k;
}Q[400005];
int main() {
Init();
int Case,n,k;
read(Case);
for(int i=1;i<=Case;i++)
{
read(Q[i].n); read(Q[i].k);
n=max(n,Q[i].n); k=max(k,Q[i].k);
}
for(N=1,Lg=0;N<((n+1)<<1);N<<=1,Lg++);
for(int i=0;i<N;i++) To[i]=(To[i>>1]>>1)|((i&1)<<(Lg-1));
Tmp[0][0]=Tmp[0][1]=1; T[0]=0; T[1]=1;
for(int i=2;i<=n;i++) T[i]=Mul(qpow(i,i-2),Inv[i]);
for(int i=2;i<=n;i++) Tmp[0][i]=Mul(qpow(2,(1ll*i*(i-1)/2)%(MOD-1)),Inv[i]);
NTT(T,1);
for(int i=1;i<=k;i++)
{
copy_n(Tmp[i-1],N,tp);
NTT(tp,1);
for(int j=0;j<N;j++) Tmp[i][j]=Mul(tp[j],T[j]);
NTT(Tmp[i],-1);
fill(Tmp[i]+n+1,Tmp[i]+N,0);
}
for(int i=1;i<=Case;i++)
{
int Ans=0;n=Q[i].n,k=Q[i].k;
for(int j=1;j<=k;j++) Ans=Add(Ans,Mul(Mul(Tmp[j][n],Fac[n]),S[k][j]));//就是这里 Tmp[j][n] 打错
printf("%d\n",Ans);
}
return 0;
}