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,
where \(\diamond\) is an arbitrary symbol.
Similarly, class \(\mathcal Z\) consists of a single object of size \(1\). In other words,
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
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:
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)\)?
This follows from the multiplication principle, though it is not the most useful form yet. Let's transform it.
Then we apply the Taylor expansion of \(\ln(1-z)\).
Thus, we obtain
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
As a reminder, \(\mathcal Z\) is a class consisting one element with size \(1\).
So we get
Take the \(\ln\) on both sides
A popular way to eliminate \(ln\) is to take the derivative
We define the summation above \(F\)
Finally,
Omitting intermediate steps, we present the formula for \(f_n\) directly:
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
It results in this sequence.
Hence, we solve the problem with a time complexity of \(O(n\log^2 n)\).
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]
*/
作者:ShaoJia,欢迎分享本文,转载时敬请注明原文来源链接。

浙公网安备 33010602011771号