BZOJ5093 图的价值(NTT+斯特林数)
显然每个点会提供相同的贡献。于是现在只考虑1号点的贡献。若其度数为i,则在2~n号点选i个连上,剩下的边随便连,这样可以算出答案为
这个式子可以O(n)计算。发现k比较小,于是考虑如何将这个式子化为与k有关的求和。
显然前面一部分可以直接提走。考虑后面一部分的组合意义:n-1个有标号盒子里面选i个,放进去k个球的方案数
可以对这个过程进行变换:把k个球放在n-1个有标号盒子里,有球的盒子必须选,没有的可选可不选的方案数
枚举有球的盒子有多少个,可以发现答案变成了一个与k有关的式子:
S(k,i)为第二类斯特林数,也即将k个小球放进i个盒子(每个盒子非空)的方案数。
问题变为快速求斯特林数。可以用容斥原理推导出斯特林数卷积形式的通项公式:
即给盒子标上号,枚举有几个空盒。再化一下:
这样卷积形式就很明显了。用NTT算一下即可。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } #define P 998244353 #define N 300000 int n,k,a[N],v[N<<1],s[N<<1],inv[N],ans,ans2; int t,r[N<<1]; int ksm(int a,int k) { if (k==0) return 1; int tmp=ksm(a,k>>1); if (k&1) return 1ll*tmp*tmp%P*a%P; else return 1ll*tmp*tmp%P; } void inc(int &x,int y){x+=y;if (x>=P) x-=P;} void DFT(int n,int *a,int p) { for (int i=0;i<n;i++) if (i<r[i]) swap(a[i],a[r[i]]); for (int i=2;i<=n;i<<=1) { int wn=ksm(p,(P-1)/i); for (int j=0;j<n;j+=i) { int w=1; for (int k=j;k<j+(i>>1);k++,w=1ll*w*wn%P) { int x=a[k],y=1ll*w*a[k+(i>>1)]%P; a[k]=(x+y)%P;a[k+(i>>1)]=(x-y+P)%P; } } } } int main() { freopen("bzoj5093.in","r",stdin); freopen("bzoj5093.out","w",stdout); n=read(),k=read(); ans=1ll*n*ksm(2,1ll*(n-1)*(n-2)/2%(P-1))%P; n--; inv[1]=1; for (int i=2;i<=max(3,min(n,k));i++) inv[i]=(P-1ll*(P/i)*inv[P%i]%P)%P; a[0]=ksm(2,n); for (int i=1;i<=min(n,k);i++) a[i]=1ll*a[i-1]*inv[2]%P*(n-i+1)%P; v[0]=1; for (int i=1;i<=min(n,k);i++) v[i]=(P-1ll*v[i-1]*inv[i]%P)%P; s[0]=0;int facinv=1; for (int i=1;i<=min(n,k);i++) { facinv=1ll*facinv*inv[i]%P; s[i]=1ll*ksm(i,k)*facinv%P; } t=1;while (t<=(min(n,k)<<1)) t<<=1; for (int i=0;i<t;i++) r[i]=(r[i>>1]>>1)|(i&1)*(t>>1); DFT(t,s,3),DFT(t,v,3); for (int i=0;i<t;i++) s[i]=1ll*s[i]*v[i]%P; DFT(t,s,inv[3]); int p=ksm(t,P-2); for (int i=0;i<t;i++) s[i]=1ll*s[i]*p%P; for (int i=0;i<=min(n,k);i++) inc(ans2,1ll*a[i]*s[i]%P); ans=1ll*ans*ans2%P; cout<<ans; fclose(stdin);fclose(stdout); return 0; }