[BZOJ3625]小朋友和二叉树

壹、题目描述

传送门 to DBZOJ

贰、题解

只要两颗二叉树不全等,他们就不同。

\(f_i\) 表示一棵神犇二叉树,它的权值之和为 \(i\) 的方案数,设 \(T=\{c_1,c_2,...c_n\}\),那么

\[f_n=\sum_{s\in T}\sum_{i=0}^{n-s}f_if_{n-s-i} \]

边界 \(f_0=1\),因为题目说明空树也算一种方案。

对于 \(F\) 我们写出它的生成函数:

\[\begin{aligned} F(x)&=\sum_{n=0}^\infty \sum_{s\in T}\sum_{i=0}^{n-s}f_if_{n-s-i}x^n+1 \\ \end{aligned} \]

发现后是卷积形式,但是前面的 \(s\in T\) 不是连续的,我们考虑定义 \(g_i=[i\in T]\),那么就有

\[F(x)=\sum_{n=0}^\infty\sum_{i=0}^n g_i\sum_{j=0}^{n-i}f_jf_{n-s-j}x^n+1 \]

如果我们定义 \(G(x)\)\(g\) 的生成函数,不难看出 \(F(x)\) 就是:

先来两个 \(F(x)\) 卷在一起,再来一个 \(G(x)\) 和两个 \(F(x)\) 卷在一起的结果再卷一卷,其实就是

\[F=GF^2+1 \]

然后,可以得到

\[F={1\pm\sqrt{1-4G}\over 2G} \]

其中,有 \(G(0)=0,F(0)=1\),考虑

\[\lim_{x\rightarrow 0}{1+\sqrt{1-4G}\over 2G}=+\infty\neq 1 \]

所以有

\[F={1-\sqrt{1-4G}\over 2G} \]

而我们最后要求的是

\[F(x)={1-\sqrt{1-4G(x)}\over 2G(x)}\pmod{x^{m+1}} \]

但是我们发现一个大问题,由于 \(G(x)\) 的常数项是 \(0\),所以分母那一坨是没有逆元的,怎么办呢,我们考虑分子有理化,上下同时乘 \(1+\sqrt{1-4G(x)}\),得到

\[F(x)={2\over 1+\sqrt{1-4G(x)}}\pmod {x^{m+1}} \]

对于这个东西,我们只需要多项式开根和多项式逆元即可。

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

叁、代码

\(\color{red}{\text{talk is treap, show you the code}.}\)

using namespace Elaina;

const int mod=998244353;
const int g=3;
const int gi=332748118;
const int maxn=1e5;
const int maxc=1e5;
const int maxsize=131072<<2;
const int inv2=499122177;

inline int qkpow(int a, int n){
	int ret=1;
	for(; n>0; n>>=1, a=1ll*a*a%mod) if(n&1)
		ret=1ll*ret*a%mod;
	return ret;
}

// first of all you should invoke function initial in the main function
namespace NTT{
	int rev[maxsize+5];
	int G[2][55], n, invn;
	inline void initial(){
		for(int j=1; j<=50; ++j){
			G[0][j]=qkpow(g, (mod-1)/(1<<j));
			G[1][j]=qkpow(gi, (mod-1)/(1<<j));
		}
	}
	inline void prepare(const int len){
		for(n=1; n<len; n<<=1);
		invn=qkpow(n, mod-2);
		for(int i=1; i<n; ++i)
			rev[i]=(rev[i>>1]>>1)|((i&1)?(n>>1):0);
	}
	inline void ntt(vector<int>&f, const int opt){
		f.resize(n);
		for(int i=0; i<n; ++i) if(i<rev[i])
			swap(f[i], f[rev[i]]);
		for(int p=2, cnt=1; p<=n; p<<=1, ++cnt){
			int len=p>>1, w=G[opt][cnt];
			for(int k=0; k<n; k+=p){
				int buf=1;
				for(int i=k; i<k+len; ++i, buf=1ll*buf*w%mod){
					int tmp=1ll*buf*f[i+len]%mod;
					f[i+len]=(f[i]-tmp+mod)%mod;
					f[i]=(f[i]+tmp)%mod;
				}
			}
		}
		if(opt==1) for(int i=0; i<n; ++i)
			f[i]=1ll*f[i]*invn%mod;
	}
}

// len should be a pow of 2
namespace poly{
	vector<int> inv(vector<int>f, const int len){
		f.resize(len);
		
		vector<int>h; h.resize(len);
		if(len==1){
			h[0]=qkpow(f[0], mod-2);
			return h;
		}
		
		h=inv(f, len>>1);
		NTT::prepare(len<<1);
		NTT::ntt(h, 0), NTT::ntt(f, 0);
		for(int i=0; i<NTT::n; ++i)
			h[i]=(2ll*h[i]%mod+mod-1ll*f[i]*h[i]%mod*h[i]%mod)%mod;
		NTT::ntt(h, 1); h.resize(len);
		
		return h;
	}
	vector<int> sqrt(vector<int>f, const int len){
		f.resize(len);
		vector<int>h; h.resize(len);
		// 因为是 1-4G(x) 调用, 常数是 1
		if(len==1){h[0]=1; return h;}
		
		h=sqrt(f, len>>1);
		vector<int>invh=inv(h, len);
		
		NTT::prepare(len<<1);
		NTT::ntt(f, 0); NTT::ntt(h, 0); NTT::ntt(invh, 0);
		for(int i=0; i<NTT::n; ++i)
			h[i]=(1ll*f[i]*invh[i]%mod*inv2%mod+1ll*h[i]*inv2%mod)%mod;
		NTT::ntt(h, 1);
		return h;
	}
}

int n, m;
int t[maxn+5];
vector<int>G;

inline void input(){
	n=readin(1), m=readin(1);
	int c;
	for(int i=1; i<=n; ++i){
		c=readin(1);
		t[c]=1;
	}
	for(int i=0; i<=maxc; ++i)
		G.push_back(t[i]);
}

signed main(){
	NTT::initial();
	input();
	int N=maxc+1;
	int len; for(len=1; len<N; len<<=1);
	for(int i=0; i<N; ++i) G[i]=(mod-4ll*G[i]%mod)%mod;
	++G[0];
	G=poly::sqrt(G, len);
	G.resize(N); ++G[0];
	G=poly::inv(G, len); G.resize(N);
	for(int i=1; i<=m; ++i)
		writc((G[i]+G[i])%mod,'\n');
	return 0;
}

肆、用到の小 \(\tt trick\)

把多项式求逆元和开根都推一下。

多项式求逆

要求 \(F(x)G(x)\equiv 1\pmod {x^{2n}}\).

假设已知 \(F(x)H(x)\equiv 1\pmod {x^n}\),那么有

\[\begin{aligned} F(x)(H(x)-G(x))&\equiv 0\pmod{x^n}\\ \Rightarrow H(x)-G(x)&\equiv 0\pmod{x^n} \\ \Rightarrow H^2(x)+G^2(x)-2H(x)G(x)&\equiv 0\pmod{x^{2n}} \\ \Rightarrow AH^2(x)+G(x)-2H(x)&\equiv 0\pmod{x^{2n}} \\ \Rightarrow G(x)&\equiv 2H(x)-AH^2(x)\pmod{x^{2n}} \\ \end{aligned} \]

边界就是常数项的逆元,这个东东用倍增。

多项式开根

前提是常数项必须存在二次剩余。

我们要求 \(G^2(x)\equiv F(x)\pmod{x^{2n}}\).

假设已知 \(H^2(x)\equiv F(x)\pmod{x^n}\),那么有(下文简写函数)

\[\begin{aligned} G^2-H^2&\equiv 0\pmod {x^n} \\ \Rightarrow (G^2-H^2)^2&\equiv 0\pmod{x^{2n}} \\ \Rightarrow (G^2+H^2)^2&\equiv 4G^2H^2\pmod{x^{2n}} \\ \Rightarrow G^2+H^2&\equiv 2GH\pmod{x^{2n}} \\ \Rightarrow A+H^2&\equiv 2GH\pmod{x^{2n}} \\ \Rightarrow G&\equiv {A+H^2\over 2H}\pmod{x^{2n}} \\ \Rightarrow G&\equiv {A\over 2H}+{H\over 2}\pmod{x^{2n}} \\ \end{aligned} \]

边界就是常数项开根(同余意义下使用二次剩余),这个东东用倍增。

另外

对于这类较为抽象的问题,我们可以考虑先将所有答案看做一个答案序列,然后列出转移方程,发现可以使用生成函数之后才使用生成函数,而不是直接使用,一般生成函数没有那么直接。

posted @ 2021-02-19 17:08  Arextre  阅读(42)  评论(0编辑  收藏  举报