有依赖的辅助多项式分治卷积
\[ g_n=\bigoplus_{i=1}^nf_i\\ f_n=\sum_{i=1}^{n-1}f_ig_{n-i} \]
已知 \(f_0=g_0=0,f_1=1\),求 \(f_2\sim f_n\)。对 \(998244353\) 取模。
\(n \leq 10^5\)。
题解
这类问题的特点:
只有 \(f_1\sim f_n\) 都求出后,\(g_n\) 才能被算出。
卷积的时候不需要 \(g_n\)。
普通的分治卷积策略是:
\[ (f_l\sim f_{mid})\times (g_1\sim g_{r-l}) \rightarrow (f_{mid+1}\sim f_r) \]
在这个问题中,当 \(l=1\) 的时候,\(g_{r-l}\) 未被算出。所以不能这样卷积。
特殊的分治卷积策略是:
-
\(l=1\) 时,
\[ (f_l\sim f_{mid})\times (g_l\sim g_{mid})\rightarrow (f_{mid+1}\sim f_r)\\ \]
这样计算的话只有 \(f_{mid+1}\) 的结果是准确的。其余的不仅没有考虑 \(f_{mid+1}\sim f_{i-1}\) 对 \(f_{i}\) 的贡献,还没有考虑 \(g_{mid+1}\sim g_{i-1}\) 对它的贡献。
-
\(l>1\) 时,
\[ \left. \begin{align*} (f_l\sim f_{mid})\times (g_1\sim g_{r-l})\\ (g_l\sim g_{mid})\times (f_1\sim f_{r-l}) \end{align*} \right\} \rightarrow (f_{mid+1}\sim f_r) \]
这个时候 \(g_{r-l}\) 和 \(f_{r-l}\) 都被算出来了,刚好可以补全差值。
时间复杂度 \(O(n \log^2 n)\)。
无根树计数
求 \(n\) 个点的无标号无根树数量,答案对 \(998244353\) 取模。
\(n \leq 2\times 10^5\)。
https://www.luogu.com.cn/blog/wengweijie/solution-p5900
欧拉变换简介
为了方便,这里介绍一下幂级数的欧拉变换 \(\mathcal{E}\)。欧拉变换的其中一个定义式如下:
\[ \mathcal{E}(F(x))=\prod_{i=1}^\infty\left(\frac{1}{1-x^i}\right)^{f_i} \]
这个变换类似于EGF中的 \(\exp\)。
\[ \exp F(x)=\sum_{i=0}^\infty \frac{F^i(x)}{i!} \]
EGF的 \(\exp\) 具有的组合意义是:将 \(1\sim n\) 分成若干非空组,大小为 \(i\) 的组内部具有 \(f_i\) 中方案,问最后的总方案数。
而欧拉变换就是去除了这个 \(1\sim n\) 的标号的方案数,去掉标号会导致“如果两个组大小和内部方案均相同,则它们不可区分”。
因此欧拉的第一个定义式的意思就是说大小为 \(i\) 的每种内部方案都可以选若干个,每种内部方案的生成函数都是 \(\frac{1}{1-x^i}\)。
现在使用指数、对数方法可以得到欧拉变换的第二个定义式。
\[ \mathcal{E}(F(x))=\exp\left(\sum_{i=1}^\infty\ln\left(\frac{1}{1-x^i}\right)^{f_i}\right)\\ =\exp\left(\sum_{i=1}^\infty-f_i\ln(1-x^i)\right)\\ =\exp\left(\sum_{i=1}^\infty f_i\sum_{j=1}^\infty\frac{x^{ij}}j\right)\\ =\exp\left(\sum_{j=1}^\infty\frac 1j\sum_{i=1}^\infty f_ix^{ij}\right)=\exp\left(\sum_{j=1}^\infty\frac{F(x^j)}j\right) \]
无标号有根树计数
容易发现,给定根以后,所有的子树可以理解成构成总大小为 \(n-1\) 的若干个组。
因此我们需要解的方程即:
\[ F(x)=x\mathcal{E}(F(x))=x\exp\left(\sum_{i=1}^\infty -f_i\ln(1-x^i)\right)\\ \]
有 \(\exp\) 不好处理,求 \(\ln\)。
\[ \ln F(x)=\ln x+\sum_{i=1}^\infty -f_i\ln(1-x^i) \]
\(\ln\) 也不好处理,求导。
\[ \frac{F'(x)}{F(x)}=\frac{1}{x}+\sum_{i=1}^\infty if_i\frac{x^{i-1}}{1-x^i} \]
通分,两侧都乘上 \(xF(x)\)。
\[ xF'(x)=F(x)+F(x)\sum_{i=1}^\infty if_i\frac{x^i}{1-x^i}\\ \]
其中
\[ \sum_{i=1}^\infty if_i\frac{x^i}{1-x^i}=\sum_{i=1}^\infty if_i\sum_{j=1}^\infty x^{ji}=\sum_{j=1}^\infty x^jF'(x^j) \]
设 \(G(x)=\sum_{i=1}^\infty x^iF'(x^i)\)。可以观察到
\[ g_n=\sum_{i|n}if_i\\ nf_n=f_n+\sum_{i=1}^{n-1}f_ig_{n-i}\\ f_n=\frac{1}{n-1}\left(\sum_{i=1}^{n-1}f_ig_{n-i}\right) \]
其中 \(f_1=1\)。此时便可以使用“有依赖的辅助多项式分治卷积”解决这个问题。
无标号无根树计数
这个问题思路大概是:用有根树的方案减去根不是重心的方案。
-
当 \(n\) 是奇数时:
如果根不是重心,必然存在恰好一个子树,它的大小超过 \(\left\lfloor\frac n2\right\rfloor\)(设它的大小为 \(k\)),那么这个子树和这棵子树以外的其他点的方案数恰好为 \(f_kf_{n-k}\)(可以看成两棵有根树)。
因此答案为
\[ ans=f_n-\sum_{k=\lfloor n/2\rfloor+1}^{n-1}f_kf_{n-k} \]
-
当 \(n\) 是偶数时:
出现的问题是,有可能存在两个重心,且其中一个是根(即存在一棵子树大小恰为 \(\frac n2\))。
那么如果这个子树和剥离该子树后的树完全相同,这样的方案在 \(f_n\) 只会计算一次,即不需要减去;如果这个子树和另一棵树不相同,会被统计两次,需要减掉一次。
因此偶数时额外减掉的方案数为 \(\binom{f_{n/2}}2\)。
至此,这个问题在 \(O(n\log^2n)\) 的时间复杂度内解决。
CO int N=524288;
int omg[2][N],rev[N];
void NTT(poly&a,int dir){
int lim=a.size(),len=log2(lim);
for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int i=1;i<lim;i<<=1)
for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
int t=mul(omg[dir][N/(i<<1)*k],a[j+i+k]);
a[j+i+k]=add(a[j+k],mod-t),a[j+k]=add(a[j+k],t);
}
if(dir==1){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
}
}
poly operator*(poly a,poly b){
int n=a.size()-1,m=b.size()-1;
int lim=1<<(int)ceil(log2(n+m+1));
a.resize(lim),NTT(a,0);
b.resize(lim),NTT(b,0);
for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
NTT(a,1),a.resize(n+m+1);
return a;
}
int n,inv[N],f[N],g[N];
void solve(int l,int r){
if(r-l+1<=100){
for(int i=l;i<=r;++i){
for(int j=l;j<i;++j){
f[i]=add(f[i],mul(f[j],g[i-j]));
if(l==1) continue;
f[i]=add(f[i],mul(g[j],f[i-j]));
}
f[i]=mul(f[i],inv[i-1]);
int v=mul(f[i],i);
for(int j=i;j<=n;j+=i) g[j]=add(g[j],v);
}
return;
}
int mid=(l+r)>>1;
solve(l,mid);
if(l==1){
poly a=poly(f+1,f+mid+1)*poly(g+1,g+mid+1);
for(int i=mid+1;i<=r;++i) f[i]=add(f[i],a[i-2]);
}
else{
poly a=poly(f+l,f+mid+1)*poly(g+1,g+r-l+1);
poly b=poly(g+l,g+mid+1)*poly(f+1,f+r-l+1);
for(int i=mid+1;i<=r;++i) f[i]=add(f[i],add(a[i-l-1],b[i-l-1]));
}
solve(mid+1,r);
}
int main(){
omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
for(int i=2;i<N;++i){
omg[0][i]=mul(omg[0][i-1],omg[0][1]);
omg[1][i]=mul(omg[1][i-1],omg[1][1]);
}
read(n);
inv[0]=inv[1]=1;
for(int i=2;i<N;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
f[1]=1,solve(1,n);
int ans=f[n];
for(int i=n/2+1;i<n;++i) ans=add(ans,mod-mul(f[i],f[n-i]));
if(n%2==0) ans=add(ans,mod-(int64)f[n/2]*(f[n/2]-1)/2%mod);
printf("%d\n",ans);
return 0;
}