bzoj3625-小朋友和二叉树

题目

给出\(n\)个互异的正整数序列,\(c_1,c_2...c_n\)。定义一颗带点权树的权值为所有点的权值和。给出一个整数\(m\),请计算对于所有的\(i=1,2,3...,m\),用\(c\)中的数能组成多少不同的二叉树(每个数可以重复使用)且二叉树的权值为\(i\)。答案对\(7*17*2^{23}+1=998244353\)取模。

Sample Input

对于\(n=2,c=\{1,2\},i=3\),有九个这样的二叉树:
sample

分析

其实看到那个模数就可以想到要用到\(NTT\)了。
可以说第一次遇到这种题,但这种生成函数法的思想以前有接触过。
两个二叉树不同就是不全等,所以我们可以分成三部分,当前点,左子树和右子树。我们设一个函数\(f\)\(f_i\)表示一个子树的点权和为\(i\)的情况数。再设\(v_i\)为权值\(i\)的个数,即有或没有。那么有:

\[\begin{aligned} f_0&=1 \\ f_k&=\sum _{i=0}^kv_i\sum _{j=0}^{k-i}f_jf_{k-i-j} \end{aligned} \]

这个式子其实可以用一个生成函数来表示。生成函数是指一种关于另一个函数\(g\)的形式函数,它的\(x^i\)项的系数为\(g(i)\),可以这样表示:

\[G(x)=\sum _{i=0}^{\infty}f(i)x^i \]

我们设\(F\)\(f\)的生成函数,\(C\)\(v\)的生成函数,那么有:

\[F(x)=C(x)*F(x)*F(x)+1 \]

那个1是从\(f_0=1\)来的。
我们要求的是\(F\)\(1-m\)项的系数,所以要把\(F\)解出来。

\[\begin{aligned} F(x)=C(x)*F(x)*F(x)+1 \\ F(x)=\frac{1\pm\sqrt{1-4C(x)}}{2C(x)} \end{aligned} \]

通过F(0)=1判断取值,若取正号:

\[\begin{aligned} \lim _{x\to 0}F(x)&=\frac{1+\sqrt{1-4C(x)}}{2C(x)} \\ &=\infty \end{aligned} \]

矛盾,所以不可取。若取负号:

\[\begin{aligned} \lim _{x\to 0}F(x)&=\frac{1-\sqrt{1-4C(x)}}{2C(x)} \\ \lim _{x\to 0}F(x)&=\frac{4C(x)}{2C(x)(1+\sqrt {1-4C(x)})} \\ \lim _{x\to 0}F(x)&=\frac{2}{1+\sqrt {1-4C(x)}} \\ &=1 \end{aligned} \]

所以我们要计算的\(F\)就是:

\[\begin{aligned} F(x)&=\frac{1-\sqrt{1-4C(x)}}{2C(x)} \\ F(x)&=\frac{2}{1+\sqrt {1-4C(x)}} \\ \end{aligned} \]

这里涉及到了多项式模意义下的求逆与开根。

多项式开根

运用分治和\(NTT\)即可在\(O(nlog^2n)\)的时间内实现。
考虑边界情况,如果多项式只有常数项,那么它的开根就是常数的开根。接下来考虑分治。
我们记符号\([k]\)表示多项式的\(0...k-1\)项模意义下同余。假设我们已经求出了\([\frac{n}{2}]\)的答案,现在考虑怎样求\([n]\)的。

\[\begin{aligned} 对F(x)开根 已知:G(x)^2=F(x) &&[\frac{n}{2}] \\ 求:H(x)^2=F(x) &&[n] \\ H(x)^2-G(x)^2=0 &&[\frac{n}{2}] \\ (H(x)^2-G(x)^2)^2=0 &&[n] \\ (H(x)^2+G(x)^2)^2=4H(x)^2G(x)^2 &&[n] \\ H(x)=\frac{H(x)^2+G(x)^2}{2G(x)} &&[n] \\ H(x)=\frac{F(x)+G(x)^2}{2G(x)} &&[n] \\ H(x)=\frac{F(x)}{2G(x)}+\frac{G(x)}{2} &&[n] \\ \end{aligned} \]

第二行,由于\(H(x)\)满足\(0...n-2\)都符合要求,那么它一定满足\(0..\frac{n}{2}-1\)项的要求。
这里需要解释一下为什么第四行处平方后模数也跟着平方。由于第三行两个函数平方相减为0,所以对于所有的\(x\)都成立,所以只能是系数全为0。因此,后面在平方的时候,对于\(0...n-1\)项,它们得出的时候必定会有之前的\(0...{\frac{n}{2}-1}\)的因数,所以平方后\(0...n-1\)项也全为0。
多项式可以开根的条件就是常数项在模意义下可以开根,所以判断起来会比较麻烦,还要用到离散对数什么的。

多项式求逆

同样思路,我们考虑边界情况,如果多项式只有常数项,那么那么它的逆就是常数项的逆元。同样考虑分治,我们已经求出了能满足前一半的结果,现在考虑怎样求能够满足整个区间的结果。

\[\begin{aligned} 对F(x)求逆 \\ 已知:G(x)F(x)=1 &&[\frac{n}{2}] \\ 求:H(x)F(x)=1 &&[n] \\ (H(x)-G(x))F(x)=0 &&[\frac{n}{2}] \\ \because F(x)\ne 0 \\ \therefore H(x)-G(x)=0 &&[\frac{n}{2}] \\ \therefore (H(x)-G(x))^2=0 &&[n] \\ H(x)^2-2H(x)G(x)+G(x)^2=0 & &[n] \\ 移项,两边同时乘以F(x) \\ H(x)=2G(x)-F(x)G(x)^2 &&[n] \end{aligned} \]

就这样直接递归计算一下求逆和开根即可,也可以写成非递归的。

代码

调了好久,主要在于之前写的时候每次传入的\(len\)没有想清楚要不要左移右移,导致了一大堆错误。其实这种情况下重新写会更好。
小视野上两秒一个点跑过去了,bzoj不行,codeforces过了。

#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long giant;
giant read() {
	giant x=0,f=1;
	char c=getchar();
	for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
	for (;isdigit(c);c=getchar()) x=x*10+c-'0';
	return x*f;
}
const giant q=998244353;
const giant g=3;
const giant maxj=19;
const giant maxn=(1<<maxj)+1;
giant c[maxn],xj,go[maxn];
giant ws[maxj+1][2];
giant mi(giant x,giant y) {
	giant ret=1;
	while (y) {
		if (y&1) (ret*=x)%=q;
		y>>=1,(x*=x)%=q;
	}
	return ret;
}
giant inv(giant x) {
	return mi(x,q-2);
}
void ntt(giant a[],giant M,giant o=1) {
	go[0]=0;
	for (xj=0;(1<<xj)!=M;++xj);
	for (giant i=0;i<M;++i) go[i]=(go[i>>1]>>1)|((i&1)<<(xj-1));
	for (giant i=0;i<M;++i) if (i<go[i]) swap(a[i],a[go[i]]);
	for (giant i=2,th=1;i<=M;i<<=1,++th) {
		for (giant j=0;j<M;j+=i) {
			giant w=1;
			for (giant k=j;k<j+i/2;++k,(w*=ws[th][o])%=q) {
				giant u=a[k]%q,v=(a[k+i/2]*w)%q;
				a[k]=(u+v+q)%q,a[k+i/2]=((u-v+q)%q+q)%q;
			}
		} 	
	}
	if (!o) {
		giant tmp=inv(M);
		for (giant i=0;i<M;++i) (a[i]*=tmp)%=q;
	}
}
giant g1[maxn],g2[maxn],f[maxn],ia[maxn],ra[maxn],ig1[maxn],ig2[maxn],iif[maxn];
void Inv(giant a[],giant len) {
	if (len==1) {
		ia[0]=inv(a[0]);
		return; 
	}
	Inv(a,len>>1);
	for (giant i=0;i<(len>>1);++i) ig1[i]=ig2[i]=ia[i];
	for (giant i=0;i<len;++i) iif[i]=a[i];
	ntt(iif,len<<1),ntt(ig2,len<<1),ntt(ig1,len<<1); //here
	for (giant i=0;i<(len<<1);++i) {
		iif[i]=((2*ig1[i]%q-(ig2[i]*ig2[i]%q)*iif[i]%q)%q+q)%q; //here
	}
	ntt(iif,len<<1,0); //here
	for (giant i=0;i<len;++i) ia[i]=iif[i];
	for (giant i=0;i<(len<<1);++i) iif[i]=ig1[i]=ig2[i]=0; //here
}
void Root(giant a[],giant len) { 
	if (len==1) {
		ra[0]=sqrt(a[0]);
		return;
	}
	Root(a,len>>1);
	giant it=inv(2);
	for (giant i=0;i<len;++i) f[i]=a[i];
	for (giant i=0;i<(len>>1);++i) {
		g1[i]=(ra[i]*it)%q;
		g2[i]=(ra[i]*2)%q;
	}
	Inv(g2,len);
	ntt(f,len<<1),ntt(ia,len<<1); // here
	for (giant i=0;i<(len<<1);++i) (f[i]*=ia[i])%=q;
	ntt(f,len<<1,0); // here
	for (giant i=0;i<len;++i) ra[i]=(g1[i]+f[i])%q;
	for (giant i=0;i<(len<<1);++i) f[i]=g1[i]=g2[i]=ia[i]=0; //here
}
int main() {
	#ifndef ONLINE_JUDGE
		freopen("test.in","r",stdin);
	#endif
	giant n=read(),m=read();
	giant tmp=1;
	while (tmp<=(m<<1)) tmp<<=1;
	for (giant i=1;i<=n;++i) c[read()]++;
	for (giant i=0;i<tmp;++i) c[i]=((c[i]*-4)%q+q)%q;
	for (giant i=0;i<=maxj;++i) {
		ws[i][1]=mi(g,(q-1)/(1<<i));
		ws[i][0]=inv(ws[i][1]);
	}
	(c[0]+=1)%=q;
	Root(c,tmp>>1);
	(ra[0]+=1)%=q;
	Inv(ra,tmp);
	for (giant i=1;i<=m;++i) (ia[i]*=2)%=q;
	for (giant i=1;i<=m;++i) printf("%lld\n",ia[i]);
}
posted @ 2017-04-17 20:17  permui  阅读(775)  评论(2编辑  收藏  举报