[数学记录]P5900 无标号无根树计数
command_block
2020-02-29 20:37:46
神仙题.jpg = 大佬口中的经典题.bmp
$\texttt{stO Weng\_Weijie Orz}$
$\texttt{stO iostream Orz}$
**广告** : [多项式计数杂谈](https://www.luogu.com.cn/blog/command-block/sheng-cheng-han-shuo-za-tan)
- ## 无标号有根树
设 $\mathcal T$ 为无标号有根树的组合类,有 :
$$\mathcal{T=Z\times {\rm MSET}(T)}$$
写成生成函数形式 :
$$
\begin{aligned}
T(x)&=x{\,\rm Exp\,}T(z)\\
&=x\exp\sum\limits_{i=1}\frac{T(x^i)}{i}
\end{aligned}
$$
- **法1** : 牛顿迭代
有 $T_*(x)={\bf T}(x)\pmod{x^n}$ 欲求 $T(x)={\bf T}(x)\pmod{x^{2n}}$
注意到,对于 $T(x^k)\pmod{x^{2n}}$ ,当 $k>2$ 时已经可以从 $T^*(x)$ 中得到。
计算 $P(x)=\exp\sum\limits_{i=2}\frac{T(x^i)}{i}$
同样有 $P^*(x)={\bf P}(x)\pmod{x^n}$ 欲求 $P(x)={\bf P}(x)\pmod{x^{2n}}$
注意到, $\exp(a+b)=\exp(a)\exp(b)$
则有 $T(x)=P(x)\exp T(x)$
接下来就可以牛顿迭代了,然而要写 $\exp$ 所以在代码量和时效上都不优。
考虑去掉 $\exp$ ,得 $\ln T(x)=\ln P(x)+T(x)$
即解 $T(x)-\ln T(x)-\ln P(x)=0$
这样子就不用`EXP`了,省下一堆常数&代码量。
- **法2** : 推式子+分治FFT
去掉$\exp$。
$$\ln T(x)=\ln x+\sum\limits_{i=1}\frac{T(x^i)}{i}$$
对 $x$ 求导去掉 $\ln$ ,注意链式法则。
- $\dfrac{T(x^i)}{i}\dfrac{d}{dx}=\dfrac{T(x^i)}{i}\dfrac{d}{dx^i}\dfrac{dx^i}{dx}=x^{i-1}T'(x^i)$
$$\dfrac{T'(x)}{T(x)}=\frac{1}{x}+\sum\limits_{i=1}x^{i-1}T'(x^i)$$
通分。
$$xT'(x)=T(x)+T(x)\sum\limits_{i=1}x^iT'(x^i)$$
- 注意$[x^n]xT'(x)=n[x^n]T(x)$
设 $G(x)=\sum\limits_{i=1}x^iT'(x^i)$ ,提取系数则有 $G[n]=\sum\limits_{i|n}iT[i]$ ( 由于枚举约数,暴力计算是$O(n\log n)$的 )。
对 $xT'(x)=T(x)+T(x)G(x)$ 提取系数:
$nT[n]=T[n]+\sum\limits_{i=0}^{n-1}T[i]G[n-i]\quad$ ( 注意到 $T[0]=G[0]=0$ )
$T[n]=\frac{1}{n-1}*\sum\limits_{i=1}^{n-1}T[i]G[n-i]\quad(n>1,T[1]=1)$
剩下的就是经典的半在线卷积了。如何计算请见 : [半在线卷积小记](https://www.luogu.com.cn/blog/command-block/ban-zai-xian-juan-ji-xiao-ji)
- ## 无标号无根树
我们考虑利用树的重心的性质,只考虑根是树的重心的方案数。
- **① $n$ 是奇数**
此时重心是惟一的,回忆重心的性质 : 每个子树的大小都不超过 $\lfloor n/2\rfloor$。
那么 “根不是重心” $\ \Longleftrightarrow\ $ 有一个子树大小超过 $\lfloor n/2\rfloor$。
使用所有方案减去不合法的部分,可得
$${\rm Ans}=T[n]-\sum\limits_{i=\lfloor n/2\rfloor+1}^{n-1}T[i]T[n-i]$$
解释 : 把根和超大的子树之间的边隔开,分成两部分计算。
- **② $n$ 是偶数**
此时还能出现双重心的情况。即恰好有一条边将树分成两个大小为 $n/2$ 的部分。
由于无标号,如果这条边两侧的树是等价的,那么根在那一半也是等价的,这部分没有重复计数。
反之,如果两个部分不同,就会计数两次。我们额外减去 $\dbinom{T[n/2]}{2}$ 即可。
```cpp
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define ull unsigned long long
#define clr(f,n) memset(f,0,sizeof(int)*(n))
#define cpy(f,g,n) memcpy(f,g,sizeof(int)*(n))
using namespace std;
const int _G=3,mod=998244353,Maxn=270000;
ll powM(ll a,int t=mod-2){
ll ans=1;
while(t){
if(t&1)ans=ans*a%mod;
a=a*a%mod;t>>=1;
}return ans;
}
const int invG=powM(_G);
int tr[Maxn],tf;
void tpre(int n){
if (tf==n)return ;tf=n;
for(int i=0;i<n;i++)
tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(int *g,bool op,int n)
{
tpre(n);
static ull f[Maxn],w[Maxn]={1};
for (int i=0;i<n;i++)f[i]=(((ll)mod<<5)+g[tr[i]])%mod;
for(int l=1;l<n;l<<=1){
ull tG=powM(op?_G:invG,(mod-1)/(l+l));
for (int i=1;i<l;i++)w[i]=w[i-1]*tG%mod;
for(int k=0;k<n;k+=l+l)
for(int p=0;p<l;p++){
int tt=w[p]*f[k|l|p]%mod;
f[k|l|p]=f[k|p]+mod-tt;
f[k|p]+=tt;
}
if (l==(1<<10))
for (int i=0;i<n;i++)f[i]%=mod;
}if (!op){
ull invn=powM(n);
for(int i=0;i<n;++i)
g[i]=f[i]%mod*invn%mod;
}else for(int i=0;i<n;++i)g[i]=f[i]%mod;
}
void px(int *f,int *g,int n)
{for(int i=0;i<n;++i)f[i]=1ll*f[i]*g[i]%mod;}
int n,m,G[Maxn],F[Maxn],s1[Maxn],s2[Maxn],s3[Maxn];
void cdq(int l,int r)
{
if (r==1)return ;
if (l+1==r){
if (l^1)F[l]=F[l]*powM(l-1)%mod;
for (int i=l;i<n;i+=l)
G[i]=(G[i]+1ll*l*F[l])%mod;
return ;
}int mid=(l+r)>>1,n=r-l;
cdq(l,mid);
if (l==0){
cpy(s1,F+l,n/2);clr(s1+(n/2),n/2);NTT(s1,1,n);
cpy(s2,G,n/2);clr(s2+(n/2),n/2);NTT(s2,1,n);
px(s1,s2,n);NTT(s1,0,n);
for (int i=n/2;i<n;i++)
F[l+i]=(F[l+i]+s1[i])%mod;
}else{
cpy(s1,F+l,n/2);clr(s1+(n/2),n/2);NTT(s1,1,n);
cpy(s2,G,n);NTT(s2,1,n);
px(s1,s2,n);cpy(s3,s1,n);
cpy(s1,G+l,n/2);clr(s1+(n/2),n/2);NTT(s1,1,n);
cpy(s2,F,n);NTT(s2,1,n);
px(s1,s2,n);for (int i=0;i<n;i++)s3[i]+=s1[i];
NTT(s3,0,n);
for (int i=n/2;i<n;i++)
F[l+i]=(F[l+i]+s3[i])%mod;
}cdq(mid,r);
}
int main()
{
scanf("%d",&m);F[1]=1;
for (n=1;n<=m;n<<=1);cdq(0,n);
ll ans=F[m];
for (int i=m/2+1;i<m;i++)
ans=(ans-1ll*F[i]*F[m-i])%mod;
if (!(m&1))ans=(ans-1ll*F[m/2]*(F[m/2]-1)/2)%mod;
printf("%lld",(ans+mod)%mod);
return 0;
}
```