[清华集训2017] 生成树计数
Posted nosta
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[清华集训2017] 生成树计数相关的知识,希望对你有一定的参考价值。
分析
一类树(连出的边数集合一定)的贡献
\[
\mathbbAns(\d_n\|\sum_id_i=2(n-1))=\prod_ia_i^d_i\prod_id_i^m\sum_id_i^m
\]
引入Prufer序列,设\(d_i\)为点(联通块)在序列中出现的次数,转换
\[
\beginaligned
\mathbbAns(\d_n\|\sum_id_i=n-2)
&=\prod_ia_i^d_i+1\prod_i(d_i+1)^m\sum_i(d_i+1)^m\&=\prod_ia_i^d_i+1(d_i+1)^m\sum_i(d_i+1)^m\\endaligned
\]
那么枚举所有的Prufer的组合,总答案
\[
\beginaligned
\mathbbAns
&=\sum_\sum_id_i=n-2\frac(n-2)!\prod_id_i!\prod_ia_i^d_i+1(d_i+1)^m\sum_i(d_i+1)^m\&=(n-2)!\prod_ia_i\sum_\sum_id_i=n-2\prod_i\fraca_i^d_i(d_i+1)^md_i!\sum_i(d_i+1)^m\&=(n-2)!\prod_ia_i(\sum_\sum_id_i=n-2\sum_i\fraca_i^d_i(d_i+1)^2md_i!\prod_i\not=j\fraca_j^d_i(d_j+1)^ma_j!)
\endaligned
\]
G8麻烦……请出生成函数
\[
A(x)=\sum_i\fracx^i(i+1)^2mi!\B(x)=\sum_i\fracx^i(i+1)^mi!\F(x)=\sum_iA(a_ix)\prod_i\not=jB(a_jx)
\]
注意到\([n-2]F(x)\)正是\(\mathbbAns\)中非常数部分(括号注明部分)。于是需要处理\(F(x)\)
\[
F(x)=\sum_i\fracA(a_ix)B(a_ix)\prod_iB(a_ix)=\sum_i\fracA(a_ix)B(a_ix)\exp(\sum_i\ln(B(a_ix)))
\]
在\(\ln\exp\)中视\(a_ix\)整体为变量,处理\(\fracA(x)B(x)\)和\(\ln(B(x))\)的系数,再用\(a_ix\)代换\(x\),求出\(\sum\fracA(a_ix)B(a_ix)\)以及\(\sum\ln(B(a_ix))\)。
这两个过程本质相同:和函数\(\sum\)的第\(i\)向系数是单个函数的\(i\)项系数(常量)乘上\(\sum_ka_k^i\)。
于是涉及到一个序列的幂和,它的生成函数
\[
f(x)=\sum_i\sum_ja_j^ix^i=\sum_i\sum_j(a_ix)^j=\sum_i\frac11-a_ix\g(x)=\sum_i\ln(\frac11-a_ix)=\sum_i\frac-a_i1-a_ix=-\sum_i\sum_ja_i^j+1x^j\f(x)=n-x\times g(x)\g(x)=\sum_i\ln((1-a_ix)^-1)=-\ln(\prod_i1-a_ix)
\]
关于这个\(\ln\)视\(x\)为变量而非\(a_ix\),于是分治FFT处理\(g(x)\),设\(L\)为不小于\(n\)的2的幂,可以粗略的估计为复杂度
\[
\sum_d^\log L\fracLdd\log d=L\sum_d^\log L\log d=L\log(\log(L)!)
\]
打表发现\(\log(\log(L)!)\)较\(\log(L)\)略大,远小于\(\log^2\)所在规模,于是近似认为复杂度为\(O(n\log n)\)
最后慢慢推回去……
实现
有很多波折……目前洛谷rank1
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200000;
const int MOD=998244353;
inline int qpow(int x,int y)
int c=1;
for(; y; y>>=1,x=(ll)x*x%MOD) if(y&1) c=(ll)c*x%MOD;
return c;
//------- POLYNOMIAL BEGIN -------
int w[N],rev[N],_inv[N],lmt;
inline void preDone(int len)
int l=0; lmt=1; _inv[1]=1;
while(lmt<=len) lmt<<=1,l++;
for(int i=0; i<lmt; ++i)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(l-1));
if(i>1) _inv[i]=(ll)_inv[MOD%i]*(MOD-MOD/i)%MOD;
int wlmt=qpow(3,(MOD-1)>>l),tmp=lmt>>1; w[tmp]=1;
for(int i=tmp+1; i<lmt; ++i) w[i]=(ll)w[i-1]*wlmt%MOD;
for(int i=tmp-1; i>0; --i) w[i]=w[i<<1];
lmt=l;
inline void DFT(int a[],int len)
static unsigned long long tmp[N];
int u=lmt-__builtin_ctz(len),T;
for(int i=0; i<len; ++i) tmp[rev[i]>>u]=a[i];
for(int m=1; m<len; m<<=1)
for(int i=0,s=m<<1; i<len; i+=s)
for(int j=0; j<m; ++j)
T=tmp[i+j+m]*w[m+j]%MOD,tmp[i+j+m]=tmp[i+j]+MOD-T,tmp[i+j]+=T;
for(int i=0; i<len; ++i) a[i]=tmp[i]%MOD;
inline void IDFT(int a[],int len)
reverse(a+1,a+len); DFT(a,len);
ll T=MOD-(MOD-1)/len;
for(int i=0; i<len; ++i) a[i]=T*a[i]%MOD;
inline int getLen(int len)
return 1<<(32-__builtin_clz(len));
inline void getDer(int a[],int b[],int n)
for(int i=0; i<n-1; ++i) b[i]=(ll)(i+1)*a[i+1]%MOD; b[n-1]=0;
inline void getInt(int a[],int b[],int n)
for(int i=n-1; i>0; --i) b[i]=(ll)_inv[i]*a[i-1]%MOD; b[0]=0;
inline void getInv(int a[],int b[],int n)
static int tmp[N];
if(n==1) b[0]=qpow(a[0],MOD-2); return;
getInv(a,b,(n+1)>>1);
int len=getLen(n<<1);
for(int i=0; i<n; ++i) tmp[i]=a[i];
for(int i=n; i<len; ++i) tmp[i]=0;
DFT(tmp,len); DFT(b,len);
for(int i=0; i<len; ++i) b[i]=(ll)b[i]*(2+MOD-(ll)b[i]*tmp[i]%MOD)%MOD;
IDFT(b,len);
for(int i=n; i<len; ++i) b[i]=0;
inline void getLn(int a[],int b[],int n)
static int tmp[N];
getInv(a,tmp,n);
getDer(a,b,n);
int len=getLen(n<<1);
DFT(tmp,len); DFT(b,len);
for(int i=0; i<len; ++i) tmp[i]=(ll)b[i]*tmp[i]%MOD;
IDFT(tmp,len);
getInt(tmp,b,n);
for(int i=n; i<len; ++i) b[i]=0;
for(int i=0; i<len; ++i) tmp[i]=0;
inline void getExp(int a[],int b[],int n)
static int tmp[N];
if(n==1) b[0]=1; return;
getExp(a,b,(n+1)>>1);
getLn(b,tmp,n);
int len=getLen(n<<1);
for(int i=0; i<n; ++i) tmp[i]=((i==0)+MOD-tmp[i]+a[i])%MOD;
for(int i=n; i<len; ++i) tmp[i]=0;
DFT(tmp,len); DFT(b,len);
for(int i=0; i<len; ++i) b[i]=(ll)tmp[i]*b[i]%MOD;
IDFT(b,len);
for(int i=n; i<len; ++i) b[i]=0;
for(int i=0; i<len; ++i) tmp[i]=0;
//------- POLYNOMIAL END -------
int n,m,con,a[N],d[20][N];
void solve(int l,int r,int p)
if(l==r)
d[p][0]=1;
d[p][1]=MOD-a[l];
return;
int mid=(l+r)>>1;
solve(l,mid,p);
solve(mid+1,r,p+1);
int len=getLen(r-l+1);
for(int i=mid-l+2; i<len; ++i) d[p][i]=0;
for(int i=r-mid+1; i<len; ++i) d[p+1][i]=0;
DFT(d[p],len); DFT(d[p+1],len);
for(int i=0; i<len; ++i) d[p][i]=(ll)d[p][i]*d[p+1][i]%MOD;
IDFT(d[p],len);
int fc[N],fv[N];
int S[N],A[N],B[N],P[N],Q[N];
int main()
//freopen("filename.in","r",stdin);
fc[0]=fc[1]=fv[0]=fv[1]=1;
for(int i=2; i<N; ++i) fv[i]=(ll)fv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=2; i<N; ++i) fv[i]=(ll)fv[i-1]*fv[i]%MOD;
for(int i=2; i<N; ++i) fc[i]=(ll)fc[i-1]*i%MOD;
scanf("%d%d",&n,&m);
preDone((n+1)*4); con=fc[n-2];
for(int i=1; i<=n; ++i) scanf("%d",a+i),con=(ll)con*a[i]%MOD;
solve(1,n,0);
getLn(d[0],S,getLen(n)); //改成二的幂次似乎能缓解数组清空问题……
S[0]=n;
for(int i=1; i<=n; ++i) S[i]=(MOD-(ll)S[i]*i%MOD)%MOD;
for(int i=0; i<=n-2; ++i)
A[i]=(ll)fv[i]*qpow(i+1,2*m)%MOD;
B[i]=(ll)fv[i]*qpow(i+1,m)%MOD;
getInv(B,P,n-1);
getLn(B,Q,n-1);
int len=getLen(n<<1);
DFT(A,len); DFT(P,len);
for(int i=0; i<len; ++i) P[i]=(ll)A[i]*P[i]%MOD;
IDFT(P,len);
for(int i=n-1; i<len; ++i) P[i]=0;
for(int i=0; i<=n-2; ++i)
P[i]=(ll)P[i]*S[i]%MOD;
Q[i]=(ll)Q[i]*S[i]%MOD;
memset(B,0,sizeof B); //这儿也是……
getExp(Q,B,n-1);
DFT(P,len); DFT(B,len);
for(int i=0; i<len; ++i) P[i]=(ll)P[i]*B[i]%MOD;
IDFT(P,len);
printf("%lld\n",(ll)con*P[n-2]%MOD);
return 0;
以上是关于[清华集训2017] 生成树计数的主要内容,如果未能解决你的问题,请参考以下文章