题解 P5900 【无标号无根树计数】

Rui_R

2020-12-21 20:01:52

Solution

题意:题如其名。 [原题](https://www.luogu.com.cn/problem/P5900) 这里提供一份被分治 $\text{NTT}$ 两只 $\log$ 吊着锤的单 $\log$ 牛顿迭代题解。 首先是 $\text{Euler}$ 变换: 让我们把一些无标号物品分成若干组(非空),大小为 $k$ 的组内部有 $f_k$ 种方案,求方案数。 对于无标号问题,如果出现大小与方案皆相同的两组,则它们没有区别。也就是说,对于大小、方案皆相同的几组,形成它们的方案数为 $1$ 。 那么,枚举大小 $k$ ,固定一种方案,则得到一个生成函数 $\sum_{i=0}\limits x^{ki} = \dfrac{1}{1-x^k}$ 对于每个大小 $k$,方案数为 $f_k$ ,那么最后总方案数的生成函数就是 $$\prod_{k=1} (\frac{1}{1-x^k})^{f_k}$$ 即 $$\text{Euler}(f(x)) = \prod_{k=1} (\frac{1}{1-x^k})^{[x^k]f(x)}$$ 我们可以把它化成另一个更实用的形式: $$\text{Euler}(f(x)) = \exp (\sum_{k=1} \ln( (\frac{1}{1-x^k})^{[x^k]f(x)}))=\exp (\sum_{k=1}[x^k]f(x) \cdot \ln(\frac{1}{1-x^k}))$$ 做过付公主的背包的同志应该知道: $$\ln(\frac{1}{1-x^k})=\sum_{t=1} \frac{x^{kt}}{t}$$ 如果没做过,可以先将其求导,一顿操作,然后积回来得到上式。 那么, $$\text{Euler}(f(x))=\exp(\sum_{k=1}[x^k]f(x)\cdot (\sum_{t=1}\frac{x^{kt}}{t}))$$ 改变求和顺序,先枚举 $t$ ,可以得到: $$\text{Euler}(f(x))=\exp(\sum_{t=1} \frac{f(x^t)}{t})$$ 先来看看无标号有根树的计数: $$g(x) = x \cdot \text{Euler}(g(x))$$ $$\frac{g(x)}{x} = \exp(\sum_{t=1}\frac{g(x^{t})}{t})$$ 两边一起取 $\ln$,得到 $$\ln(\frac{g(x)}{x})=\sum_{t=1}\frac{g(x^t)}{t}$$ 设函数 $f$ ,且令 $$f(g(x))=\ln(\frac{g(x)}{x})-\sum_{t=1}\frac{g(x^t)}{t}$$ 对这个东西上牛顿迭代:求 $g(x)$ 使 $f(g(x))\equiv 0 \pmod{x^n}$ 假设我们递归的求出了 $g_0(x)$ ,使 $f(g_0(x))\equiv 0 \pmod{x^{\lceil \frac{n}{2} \rceil}}$ 注意到 $g(x)$ 和 $g_0(x)$ 的前 $\lceil \frac{n}{2} \rceil$ 项是一致的,而且 $\dfrac{g(x^t)}{t}$ 只会跟 $g(x)$ 的前 $\lfloor \dfrac{n}{t} \rfloor$ 项有关。 也就是说,对于 $t>=2$ ,$\dfrac{g(x^t)}{t}$ 已经被决定了。因为此处的 $g$ 完全可以用 $g_0$ 换掉。那么,令 $$\alpha = \sum_{t=2}{\frac{g_0(x^t)}{t}}$$ 这个东西可以调和级数得到。那么: $$f(g(x))=\ln(\frac{{g(x)}}{{x}})-g(x)-\alpha$$ 其中 $\alpha$ 与 $g(x)$ 无关。$\dfrac{1}{x}$ 与 $g(x)$ 也无关。 $$f'(g(x))=\frac{1}{x} \cdot \frac{1}{\frac{g(x)}{x}}-1$$ 上牛迭公式: $$g(x)\equiv g_0(x)-\frac{f(g_0(x))}{f'(g_0(x))} \pmod {x^n}$$ 令 $g_1(x)=\dfrac{g_0(x)}{x}$,$g_2(x)=\dfrac{g(x)}{x}$ $$g(x)\equiv g_0(x)-\frac{\ln(g_1(x))-g_0(x)-\alpha}{\frac{1}{x}\cdot \frac{1}{g_1(x)}-1} \pmod{x^n}$$ 两边一起除掉 $x$,得到: $$g_2(x) \equiv g_1(x)-\frac{\ln(g_1(x))-g_0(x)-\alpha}{\frac{1}{g_1(x)}-x} \pmod{x^n}$$ 于是,可以对 $\dfrac{g(x)}{x}$ 进行牛顿迭代求解。从而得到 $g(x)$。 这里复杂度是 $n \log n$ 的,但是常数巨大。 最后,考虑怎么由无标号有根树得到无标号无根树:让每种情况只在根为重心时被统计一次。 也就是说,用总方案数减去根不是重心的方案数。 既然根不是重心,那么根一定会有且只有一个子树的大小大于等于 $\lfloor \dfrac{n}{2} \rfloor+1$。 枚举这个大小 $k$ ,减掉 $[x^k]g(x)\cdot [x^{n-k}]g(x)$ 就好。 同时,如果 $n$ 是个偶数,那它有可能会有两个子树大小为 $\dfrac{n}{2}$ 的重心。 如果这两个重心的子树内形状一致,则会被正确统计一次。 否则,会统计两次。于是要减掉多出来的那一半,也就是 $$\frac{[x^{\frac{n}{2}}]g(x)\cdot ([x^{\frac{n}{2}}]g(x)-1)}{2}=\binom{[x^{\frac{n}{2}}]g(x)}{2}$$ 至此结束。这个做法避免了 $\exp$ 这个常数要人命的东西,然而常数依然很大,大得被两只 $\log$ 吊起来锤。 (可能是我自带大常数……板子的实现太不优秀了) 剩下的,见代码: ``` #include <cstdio> #include <cstring> const int maxn = 6e5+5 , mod = 998244353; template<typename T> inline T max(const T &a,const T &b){ return a>b?a:b; } template<typename T> inline void swap(T &a,T &b){ T temp=a;a=b;b=temp; } inline int mul(int a,int b){return 1ll*a*b%mod;} int fastpow(int x,int y){ if(y==0) return 1; int tmp=fastpow(x,y>>1); return y&1?mul(mul(tmp,tmp),x):mul(tmp,tmp); } namespace Poly{ const int gate = 3,invg = fastpow(gate,mod-2); int lim,lg,sing[maxn]; void init(int len){ lim = 1,lg = 0;while(lim<len) lim<<=1,lg++; for(int i=1;i<lim;i++) sing[i]=(sing[i>>1]>>1)|((i&1)<<(lg-1)); } void NTT(int *Steins,int type){ for(int i=0;i<lim;i++) if(i<sing[i]) swap(Steins[i],Steins[sing[i]]); for(int len=1;len<lim;len<<=1){ int unit=fastpow(type==1?gate:invg,(mod-1)/(len<<1)); for(int i=0;i<lim;i+=(len<<1)){ int w = 1; for(int k=0;k<len;k++,w=mul(w,unit)){ int x=Steins[i+k],y=mul(w,Steins[i+k+len]); Steins[i+k]=(x+y)%mod,Steins[i+k+len]=(x-y)%mod; } } } if(type!=1){int Okabe=fastpow(lim,mod-2);for(int i=0;i<lim;i++) Steins[i]=mul(Steins[i],Okabe);} } int g[maxn]; void get_inv(int *Alpha,int len,int *Beta){ //使用前清空Beta数组 if(len==1){Beta[0]=fastpow(Alpha[0],mod-2);return ;} get_inv(Alpha,(len+1)>>1,Beta);init(len+len-1); for(int i=0;i<len;i++) g[i]=Alpha[i];for(int i=len;i<lim;i++) g[i]=0; NTT(Beta,1),NTT(g,1); for(int i=0;i<lim;i++) Beta[i]=mul(Beta[i],2-mul(Beta[i],g[i])); NTT(Beta,-1); for(int i=len;i<lim;i++) Beta[i]=0; } void convolution(int *Alpha,int la,int *Beta,int lb,int *Zeta){ init(la+lb-1);for(int i=lb;i<lim;i++) Beta[i]=0; for(int i=la;i<lim;i++) Alpha[i]=0; NTT(Alpha,1),NTT(Beta,1); for(int i=0;i<lim;i++) Zeta[i]=mul(Alpha[i],Beta[i]); NTT(Zeta,-1); for(int i=la+lb-1;i<lim;i++) Zeta[i]=0; } const int inv2=fastpow(2,mod-2); int t[maxn]; void get_sqrt(int *Alpha,int len,int *Beta){ //使用前清空Beta数组 if(len==1){Beta[0]=1;return ;} get_sqrt(Alpha,(len+1)>>1,Beta);get_inv(Beta,len,t); init(len+len-1); for(int i=0;i<len;i++) g[i]=Alpha[i];for(int i=len;i<lim;i++) g[i]=0; NTT(g,1),NTT(t,1),NTT(Beta,1); for(int i=0;i<lim;i++) Beta[i]=mul(inv2,(Beta[i]+mul(g[i],t[i]))%mod); NTT(Beta,-1); for(int i=len;i<lim;i++) Beta[i]=0;for(int i=0;i<lim;i++) t[i]=0; } void derivative(int *Alpha,int len,int *Beta){ for(int i=1;i<len;i++) Beta[i-1]=mul(i,Alpha[i]); Beta[len-1] = 0; } void integral(int *Alpha,int len,int *Beta){ for(int i=1;i<len;i++) Beta[i]=mul(Alpha[i-1],fastpow(i,mod-2)); Beta[0] = 0; } int f1[maxn],f2[maxn],f3[maxn]; void get_ln(int *Alpha,int len,int *Beta){ memset(f2,0,sizeof(f2)); derivative(Alpha,len,f1),get_inv(Alpha,len,f2); convolution(f1,len,f2,len,f3); integral(f3,len,Beta); } int e1[maxn],e2[maxn]; void get_exp(int *Alpha,int len,int *Beta){ //使用前清空Beta数组 if(len==1) return void(Beta[0]=1); get_exp(Alpha,(len+1)>>1,Beta);get_ln(Beta,len,e1); init(len+len-1); for(int i=0;i<len;i++) e2[i]=Alpha[i];for(int i=len;i<lim;i++) e2[i]=0; NTT(e2,1),NTT(Beta,1),NTT(e1,1); for(int i=0;i<lim;i++) Beta[i]=mul(Beta[i],(1-e1[i]+e2[i])); NTT(Beta,-1); for(int i=len;i<lim;i++) Beta[i]=0;for(int i=0;i<lim;i++) e1[i]=0; } int g1[maxn],g2[maxn],g3[maxn]; void division(int *Alpha,int la,int *Beta,int lb,int *Zeta,int *Gamma){ if(la<lb){ for(int i=0;i<la;i++) Gamma[i] = Alpha[i]; return ; } for(int i=0;i<la;i++) g1[i]=Alpha[la-i-1];for(int i=0;i<lb;i++) g2[i]=Beta[lb-i-1]; for(int i=lb;i<=la-lb;i++) g2[i]=0; Poly::init((la-lb+1)<<1|1);for(int i=0;i<lim;i++) g3[i]=0; get_inv(g2,la-lb+1,g3); convolution(g1,la,g3,la-lb+1,Zeta); for(int i=0;i<=la-lb;i++) if(i<la-lb-i) swap(Zeta[i],Zeta[la-lb-i]);else break; for(int i=la-lb+1;i<lim;i++) Zeta[i]=0; for(int i=0;i<lb;i++) g2[i]=Beta[i];for(int i=0;i<=la-lb;i++) g3[i]=Zeta[i]; convolution(g2,lb,g3,la-lb+1,g1); for(int i=0;i<lb;i++) Gamma[i]=(Alpha[i]-g1[i])%mod; } } int n,Alpha[maxn],Beta[maxn],Gamma[maxn]; int g1[maxn],g2[maxn]; void Newton(int len,int *Alpha){ if(len==1) return void(Alpha[0]=1); Newton((len+1)>>1,Alpha); for(int i=0;i<len;i++) Gamma[i]=0; for(int i=1;i<len;i++){ int Okabe = fastpow(i,mod-2); for(int j=1;j*i<len;j++) Gamma[j*i]=(Gamma[j*i]+mul(Okabe,Alpha[j-1]))%mod; } //注意i从1开始,这里将g_0也顺手合并了进去 memset(g1,0,sizeof(g1)); Poly::get_ln(Alpha,len,g1);for(int i=0;i<len;i++) g1[i]=(g1[i]-Gamma[i])%mod; memset(g2,0,sizeof(g2));Poly::get_inv(Alpha,len,g2); g2[1]=(g2[1]-1)%mod; memset(Beta,0,sizeof(Beta));Poly::get_inv(g2,len,Beta); Poly::convolution(g1,len,Beta,len,g1); for(int i=0;i<len;i++) Alpha[i]=(Alpha[i]-g1[i])%mod; } int main(){ scanf("%d",&n);Newton(n,Alpha);//这里得到的是除以x之后的结果 int answer = Alpha[n-1]; for(int i=(n>>1)+1;i<=n-1;i++) answer = (answer-mul(Alpha[i-1],Alpha[n-i-1]))%mod; if(!(n&1)) answer = answer-mul(fastpow(2,mod-2),mul(Alpha[n/2-1],Alpha[n/2-1]-1)); answer%=mod; printf("%d\n",(answer+mod)%mod); return 0; } ``` 最后的最后,关于另一篇牛顿迭代题解的未解之谜2,我相信那是它写法上出了什么意外,因为我这里数组开到 $6e5$ 就过了。