• LG5900 无标号无根树计数


    有依赖的辅助多项式分治卷积

    [g_n=igoplus_{i=1}^nf_i\ f_n=sum_{i=1}^{n-1}f_ig_{n-i} ]

    已知 (f_0=g_0=0,f_1=1),求 (f_2sim f_n)。对 (998244353) 取模。

    (n leq 10^5)

    题解

    这类问题的特点:

    1. 只有 (f_1sim f_n) 都求出后,(g_n) 才能被算出。

    2. 卷积的时候不需要 (g_n)

    普通的分治卷积策略是:

    [(f_lsim f_{mid}) imes (g_1sim g_{r-l}) ightarrow (f_{mid+1}sim f_r) ]

    在这个问题中,当 (l=1) 的时候,(g_{r-l}) 未被算出。所以不能这样卷积。

    特殊的分治卷积策略是:

    1. (l=1) 时,

      [(f_lsim f_{mid}) imes (g_lsim g_{mid}) ightarrow (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}) 对它的贡献。

    2. (l>1) 时,

      [left. egin{align*} (f_lsim f_{mid}) imes (g_1sim g_{r-l})\ (g_lsim g_{mid}) imes (f_1sim f_{r-l}) end{align*} ight} ightarrow (f_{mid+1}sim f_r) ]

      这个时候 (g_{r-l})(f_{r-l}) 都被算出来了,刚好可以补全差值。

    时间复杂度 (O(n log^2 n))

    无根树计数

    (n) 个点的无标号无根树数量,答案对 (998244353) 取模。

    (n leq 2 imes 10^5)

    https://www.luogu.com.cn/blog/wengweijie/solution-p5900

    欧拉变换简介

    为了方便,这里介绍一下幂级数的欧拉变换 (mathcal{E})。欧拉变换的其中一个定义式如下:

    [mathcal{E}(F(x))=prod_{i=1}^inftyleft(frac{1}{1-x^i} ight)^{f_i} ]

    这个变换类似于EGF中的 (exp)

    [exp F(x)=sum_{i=0}^infty frac{F^i(x)}{i!} ]

    EGF的 (exp) 具有的组合意义是:将 (1sim n) 分成若干非空组,大小为 (i) 的组内部具有 (f_i) 中方案,问最后的总方案数。

    而欧拉变换就是去除了这个 (1sim n) 的标号的方案数,去掉标号会导致“如果两个组大小和内部方案均相同,则它们不可区分”。

    因此欧拉的第一个定义式的意思就是说大小为 (i) 的每种内部方案都可以选若干个,每种内部方案的生成函数都是 (frac{1}{1-x^i})

    现在使用指数、对数方法可以得到欧拉变换的第二个定义式。

    [mathcal{E}(F(x))=expleft(sum_{i=1}^inftylnleft(frac{1}{1-x^i} ight)^{f_i} ight)\ =expleft(sum_{i=1}^infty-f_iln(1-x^i) ight)\ =expleft(sum_{i=1}^infty f_isum_{j=1}^inftyfrac{x^{ij}}j ight)\ =expleft(sum_{j=1}^inftyfrac 1jsum_{i=1}^infty f_ix^{ij} ight)=expleft(sum_{j=1}^inftyfrac{F(x^j)}j ight) ]

    无标号有根树计数

    容易发现,给定根以后,所有的子树可以理解成构成总大小为 (n-1) 的若干个组。

    因此我们需要解的方程即:

    [F(x)=xmathcal{E}(F(x))=xexpleft(sum_{i=1}^infty -f_iln(1-x^i) ight)\ ]

    (exp) 不好处理,求 (ln)

    [ln F(x)=ln x+sum_{i=1}^infty -f_iln(1-x^i) ]

    (ln) 也不好处理,求导。

    [frac{F'(x)}{F(x)}=frac{1}{x}+sum_{i=1}^infty if_ifrac{x^{i-1}}{1-x^i} ]

    通分,两侧都乘上 (xF(x))

    [xF'(x)=F(x)+F(x)sum_{i=1}^infty if_ifrac{x^i}{1-x^i}\ ]

    其中

    [sum_{i=1}^infty if_ifrac{x^i}{1-x^i}=sum_{i=1}^infty if_isum_{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} ight) ]

    其中 (f_1=1)。此时便可以使用“有依赖的辅助多项式分治卷积”解决这个问题。

    无标号无根树计数

    这个问题思路大概是:用有根树的方案减去根不是重心的方案。

    1. (n) 是奇数时:

      如果根不是重心,必然存在恰好一个子树,它的大小超过 (leftlfloorfrac n2 ight floor)(设它的大小为 (k)),那么这个子树和这棵子树以外的其他点的方案数恰好为 (f_kf_{n-k})(可以看成两棵有根树)。

      因此答案为

      [ans=f_n-sum_{k=lfloor n/2 floor+1}^{n-1}f_kf_{n-k} ]

    2. (n) 是偶数时:

      出现的问题是,有可能存在两个重心,且其中一个是根(即存在一棵子树大小恰为 (frac n2))。

      那么如果这个子树和剥离该子树后的树完全相同,这样的方案在 (f_n) 只会计算一次,即不需要减去;如果这个子树和另一棵树不相同,会被统计两次,需要减掉一次。

      因此偶数时额外减掉的方案数为 (inom{f_{n/2}}2)

    至此,这个问题在 (O(nlog^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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    Java源码赏析(四)Java常见注解
    Java源码赏析(三)初识 String 类
    Java源码赏析(二)Java常见接口
    Java源码赏析(一)Object 类
    Java随谈(二)对空指针异常的碎碎念
    Java随谈(一)魔术数字、常量和枚举
    jquery.validate 使用--验证表单隐藏域
    jquery.validate使用
    jquery.validate使用
    jquery.validate使用
  • 原文地址:https://www.cnblogs.com/autoint/p/12178270.html
Copyright © 2020-2023  润新知