ANALYTIC COMBINATORICS Reading Notes and Application to Counting Trees with N Unlabeled Nodes

Symbolic enumeration methods

Disclaimer: we assume the size function is globally consistent, then everything will be simpler, and it is enough for my own usage. Please do not be alarmed by unfamiliar symbols, because they are likely \mathcal of capital latin letters.

Assume that \(\alpha,\beta,\gamma\) are objects and \(\alpha=(\beta,\gamma)\), then \(|\alpha|=|\beta|+|\gamma|\).

A combinatorial class, or simply a class, is a set of objects, including notations like \(\mathcal{A,B,C,E,Z}\).

Class \(\mathcal E\) consists of a single object of size \(0\). In other words,

\[\mathcal E_\diamond=\{\diamond\}, |\diamond|=0 \]

where \(\diamond\) is an arbitrary symbol.

Similarly, class \(\mathcal Z\) consists of a single object of size \(1\). In other words,

\[\mathcal Z_\diamond=\{\diamond\}, |\diamond|=1 \]

where \(\diamond\) can be any notation.

We find that the cartesian product of classes can be easily defined similar to that of sets.

Combinatorial sum(disjoint union) can be defined as follow

\[\mathcal B+\mathcal C=(\mathcal E_\diamond\times\mathcal B)\cup(\mathcal E_\square\times\mathcal C) \]

We use corresponding letters to represent the generating function \(A(z)\) and the counting sequence \(\{a_n\}\) of class \(\mathcal A\).

For instance, \(E(z)=1,Z(z)=z\).

For \(\mathcal A=\mathcal B\times\mathcal C\implies A(z)=B(z)\cdot C(z)\).

For \(\mathcal A=\mathcal B+\mathcal C\implies A(z)=B(z)+C(z)\).

Next, we introduce a few fundamental constructions that build upon set-theoretic union and product, and form sequences, sets, and cycles.

An arbitrarily long(could be empty) sequence:

\[\mathcal G=\text{SEQ}(\mathcal F)=\mathcal E+\mathcal F+(\mathcal F\times\mathcal F)+(\mathcal F\times\mathcal F\times \mathcal F)+\dots \]

Every different object can be taken any number of times: \( \mathcal G=\text{MSET}(\mathcal F) \)

We have \(G(z)=\frac{1}{1-F(z)}\) when \(\mathcal G=\text{SEG}(\mathcal F)\), but what is the formula for \(G(z)\) if \(\mathcal G=\text{MSET}(\mathcal F)\)?

\[G(z)=\prod_i(\frac{1}{1-z^i})^{[z^i]F(z)} \]

This follows from the multiplication principle, though it is not the most useful form yet. Let's transform it.

\[\ln G(z)=-\sum_i \ln(1-z^i)\cdot [z^i]F(z) \]

Then we apply the Taylor expansion of \(\ln(1-z)\).

\[\begin{aligned} \ln G(z)&=\sum_i [z^i]F(z)\cdot \sum_{j=1}^{+\infty}\frac{z^{ij}}{j} \\ &=\sum_{j=1}^{+\infty}\frac{\sum_i [z^i]F(z)\cdot z^{ij}}{j} \\ &=\sum_{j=1}^{+\infty}\frac{F(z^j)}{j} \end{aligned} \]

Thus, we obtain

\[\boxed{G(z)=\exp\left(\sum_{i=1}^{+\infty}\frac{F(z^i)}{i}\right)} \]

We use \(G=\text{Exp}(F)\) to denote the formula above. This is also called Euler's transformation, which is an extension of Pólya theorem.

Now we are ready to solve the counting task.

number of rooted trees with \(n\) unlabeled nodes

We solve the rooted version first. The conversion from rooted version to the unrooted, which will be introduced in the next section, is easier than this part.

Define the combinatorial class of unlabeled rooted trees (with size function defined as the number of nodes) as this recursion form

\[\mathcal T=\mathcal Z\times \text{MSET}(\mathcal T) \]

As a reminder, \(\mathcal Z\) is a class consisting one element with size \(1\).

So we get

\[T=z\cdot \text{Exp}(T) \]

Take the \(\ln\) on both sides

\[\ln T(z)=\ln(z)+\sum_{i=1}^{+\infty}\frac{T(z^i)}{i} \]

A popular way to eliminate \(ln\) is to take the derivative

\[\begin{aligned} \frac{T'(z)}{T(z)}&=\frac 1z+\sum_{i=1}^{+\infty}T'(z^i)z^{i-1} \\ \frac{zT'(z)}{T(z)}&=1+\sum_{i=1}^{+\infty}T'(z^i)z^i \end{aligned} \]

We define the summation above \(F\)

\[zT'(z)=T(z)+T(z)F(z) \]

Finally,

\[t_n=\begin{cases} n ,& n\le 2 \\ \frac{\sum_i t_if_{n-i}}{n-1} ,& \text{Otherwise} \end{cases} \]

Omitting intermediate steps, we present the formula for \(f_n\) directly:

\[f_n=\sum_{i|n}i\cdot t_i \]

So far, we can utilize the Relaxed Convolution to solve it in \(O(n\log^2 n)\), more details can be found here.

Result sequence of this stage on OEIS

number of trees with \(n\) unlabeled nodes

By investigating the Tree Centroid and applying the principle of inclusion-exclusion (including discussions on the parity of number of nodes), the answer function

\[A(z)=T(z)-\frac 12T^2(z)+\frac 12T(z^2) \]

It results in this sequence.

Hence, we solve the problem with a time complexity of \(O(n\log^2 n)\).

Problem (luogu)

My code:

// #pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define rep(Ii,Jj,Kk) for(int Ii=(Jj),Ii##_=(Kk);Ii<=Ii##_;Ii++)
#define per(Ii,Jj,Kk) for(int Ii=(Jj),Ii##_=(Kk);Ii>=Ii##_;Ii--)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned uint;
typedef long double db;
#define fir first
#define sec second
#define siz(Aa) ((int)(Aa).size())
#define all(Aa) (Aa).begin(),(Aa).end()
#define ckmx(Aa,Bb) (Aa=max(Aa,Bb))
#define ckmn(Aa,Bb) (Aa=min(Aa,Bb))
template<int P>
struct mod_int{
	using Z=mod_int;
	static signed mo(signed x){return x<0?x+P:x;}
	signed x;
	signed val()const{return x;}
	mod_int():x(0){}
	template<class T>mod_int(const T&x_):x(x_>=0&&x_<P?static_cast<signed>(x_):mo(static_cast<signed>(x_%P))){}
	bool operator==(const Z&rhs)const{return x==rhs.x;}
	bool operator!=(const Z&rhs)const{return x!=rhs.x;}
	Z operator-()const{return Z(x?P-x:0);}
	Z pow(long long k)const{Z res=1,t=*this;while(k){if(k&1)res*=t;if(k>>=1)t*=t;}return res;}
	Z operator~()const{assert(x);return pow(P-2);}
	Z&operator++(){x<P-1?++x:x=0;return *this;}
	Z&operator--(){x?--x:x=P-1;return *this;}
	Z operator++(signed){Z ret=x;x<P-1?++x:x=0;return ret;}
	Z operator--(signed){Z ret=x;x?--x:x=P-1;return ret;}
	Z&operator+=(const Z&rhs){(x+=rhs.x)>=P&&(x-=P);return *this;}
	Z&operator-=(const Z&rhs){(x-=rhs.x)<0&&(x+=P);return *this;}
	Z&operator*=(const Z&rhs){x=1ULL*x*rhs.x%P;return *this;}
	Z&operator/=(const Z&rhs){return *this*=~rhs;}
#define setO(o) friend Z operator o(const Z&lhs,const Z&rhs){Z res=lhs;return res o##=rhs;}
	setO(+)setO(-)setO(*)setO(/)
#undef setO
	friend istream& operator>>(istream&is,Z&x){long long y;is>>y;x=Z(y);return is;}
	friend ostream& operator<<(ostream&os,const Z&x){return os<<x.val();}
};
const int P=119<<23|1;
using Z=mod_int<P>;

const int W=19,N=1<<W;
int tax[N];
Z t[N],f[N],g[N],t2[N],ans[N],zeta[W],iv[N];
void dft(Z*a,int w){
	int n=1<<w;
	rep(i,0,n-1) tax[i]=(tax[i>>1]>>1)|(i&1?n>>1:0);
	rep(i,0,n-1) if(tax[i]<i) swap(a[tax[i]],a[i]);
	rep(ii,0,w-1){
		int i=1<<ii;
		Z ze=zeta[ii];
		for(int j=0;j!=n;j+=(i<<1)){
			Z z=1;
			rep(k,0,i-1){
				Z x=a[j+k],y=a[j+k+i]*z;
				a[j+k]=x+y;
				a[j+k+i]=x-y;
				z*=ze;
			}
		}
	}
}
void idft(Z*a,int w){
	int n=1<<w;
	dft(a,w);
	reverse(a+1,a+n);
	Z ni=~Z(n);
	rep(i,0,n-1) a[i]*=ni;
}
void mul(Z*a,Z*b,Z*c,int w){
	int n=1<<w;
	if(n<=64){
		rep(i,0,n-1) rep(j,0,n-1) c[i+j]+=a[i]*b[j];
		return ;
	}
	static Z A[N],B[N];
	copy(a,a+n,A),fill(A+n,A+2*n,0),dft(A,w+1);
	copy(b,b+n,B),fill(B+n,B+2*n,0),dft(B,w+1);
	rep(i,0,2*n-1) A[i]*=B[i];
	idft(A,w+1);
	rep(i,0,2*n-1) c[i]+=A[i];
}
int w,n; 
void set_t(int x,Z y){
	t[x]=y;
	Z val=x*t[x];
	for(int i=x;i<n;i+=x){
		f[i]+=val;
	}
}
void work(int l,int r){// [l,r)
	if(l+1==r){
		set_t(l,g[l]*iv[l-1]);
		return ;
	}
	int mid=(l+r)>>1;
	int w=__lg(r-l)-1;
	int len=mid-l;
	work(l,mid);
	mul(t+l,f+len,g+mid,w);
	mul(t+len,f+l,g+mid,w);
	work(mid,r);
	mul(t+mid,f+len,g+r,w);
	mul(t+len,f+mid,g+r,w);
}
signed main(){ios::sync_with_stdio(false),cin.tie(nullptr);
	int idx;
	cin>>idx;
	
	w=__lg(idx)+1;
	n=1<<w;
	
	rep(i,0,w) zeta[i]=Z(3).pow((P-1)/(2<<i));
	iv[1]=1;
	rep(i,2,n-1) iv[i]=-(P/i)*iv[P%i];
	
	t[1]=1;
	rep(i,1,n-1) f[i]++;
	g[2]=1;// t*f
	rep(i,1,w-1){// [2^i,2^{i+1})
		int j=1<<i;
		work(j,2*j);
		mul(t+j,f+j,g+2*j,i);
	}
	/*
	rep(i,2,n-1){
		// t
		rep(j,1,i){
			t[i]+=t[j]*f[i-j];
		}
		t[i]*=~Z(i-1);
		
		// f
		rep(j,1,i) if(i%j==0){
			f[i]+=j*t[j];
		}
	}
	*/
	// rep(i,1,n-1) cout<<i<<":"<<t[i]<<" "<<f[i]<<"\n";
	
	copy(t,t+n,t2);
	dft(t2,w+1);
	rep(i,0,(1<<(w+1))-1) t2[i]*=t2[i];
	idft(t2,w+1);
	
	rep(i,1,n) ans[i]+=t[i]-iv[2]*t2[i];
	rep(i,1,n>>1) ans[i*2]+=iv[2]*t[i];
	
	// rep(i,1,n-1) cout<<ans[i]<<"\n";
	cout<<ans[idx]<<"\n";
return 0;}
/*
f[n]=sum i|n:t[i]
t[n]=sum i:t[i]*f[n-i]
*/
posted @ 2025-08-07 09:40  ShaoJia  阅读(22)  评论(0)    收藏  举报