zoukankan      html  css  js  c++  java
  • 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)的个数,即有或没有。那么有:

    [egin{aligned} f_0&=1 \ f_k&=sum _{i=0}^kv_isum _{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)解出来。

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

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

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

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

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

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

    [egin{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])的。

    [egin{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。
    多项式可以开根的条件就是常数项在模意义下可以开根,所以判断起来会比较麻烦,还要用到离散对数什么的。

    多项式求逆

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

    [egin{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}] \ ecause F(x) e 0 \ herefore H(x)-G(x)=0 &&[frac{n}{2}] \ herefore (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
    ",ia[i]);
    }
    
  • 相关阅读:
    SpringBoot自动装配原理解析
    面试官:你知道Spring中有哪些可以让我们扩展的地方么
    Spring事件监听机制
    Spring扩展点之Aware接口族
    程序员的进阶课-架构师之路(13)-B-树
    程序员的进阶课-架构师之路(12)-2-3-4树
    程序员的进阶课-架构师之路(11)-最容易理解的红黑树
    程序员的进阶课-架构师之路(10)-霍夫曼树
    程序员的进阶课-架构师之路(9)-平衡二叉树(AVL树)
    程序员的进阶课-架构师之路(8)-二叉树
  • 原文地址:https://www.cnblogs.com/owenyu/p/6724613.html
Copyright © 2011-2022 走看看