codeforces438E The Child and Binary Tree

题目链接

题目:

​ 给定一个长度为 n n n的序列 c c c c c c中的数互不相同,要你构造二叉树,树上的每一个点有权值,权值都是序列 c c c中的数。对于 ∀ 1 ≤ i ≤ m \forall 1 \le i \le m 1im,问权值和为 i i i的二叉树有多少棵。

( 1 ≤ c i , n , m ≤ 1 0 5 ) (1 \le c_i,n,m \le 10^5) (1ci,n,m105)

题解:

​ 首先考虑一个 d p dp dp,令 g n g_n gn为权值和为 n n n的二叉树的个数,定义数列 { f n } \{f_n\} {fn},若 f i = 1 f_i=1 fi=1,那么 i ∈ c i \in c ic,否则, i ∉ c i \notin c i/c

​ 那么可得转移方程
g n = { 1 n = 0 ∑ i = 1 n f i ∑ j + k = n − i g j g k n ≠ 0 g_n=\begin{cases} 1 & n=0 \\ \displaystyle\sum_{i=1}^nf_i\sum_{j+k=n-i}g_jg_k & n \ne 0 \end{cases} gn=1i=1nfij+k=nigjgkn=0n=0
​ 其中第二个式子可以写成 g n = ∑ i + j + k = n f i g j g k \displaystyle g_n=\sum_{i+j+k=n}f_ig_jg_k gn=i+j+k=nfigjgk,显然是一个卷积式,令 { f n } \{f_n\} {fn}的生成函数为 f ( x ) f(x) f(x) { g n } \{g_n\} {gn}的生成函数为 g ( x ) g(x) g(x),那么可以得到 g ( x ) = 1 + ∑ i = 1 n g i x i = 1 + ∑ i = 0 n ∑ i + j + k = n f i g j g k = 1 + f ( x ) g 2 ( x ) \displaystyle g(x)=1+\sum_{i=1}^ng_ix^i=1+\sum_{i=0}^n\sum_{i+j+k=n}f_ig_jg_k=1+f(x)g^2(x) g(x)=1+i=1ngixi=1+i=0ni+j+k=nfigjgk=1+f(x)g2(x)

​ 移项可得 f ( x ) g 2 ( x ) − g ( x ) + 1 = 0 f(x)g^2(x)-g(x)+1=0 f(x)g2(x)g(x)+1=0,所以 g ( x ) = 1 ± 1 − 4 f ( x ) 2 f ( x ) \displaystyle g(x)=\frac{1 \pm \sqrt{1-4f(x)}}{2f(x)} g(x)=2f(x)1±14f(x) 。由 lim ⁡ x → 0 g ( x ) = g ( 0 ) \displaystyle \lim_{x \to 0}g(x)=g(0) x0limg(x)=g(0),可得 g ( x ) = 1 − 1 − 4 f ( x ) 2 f ( x ) = 2 1 + 1 − 4 f ( x ) \displaystyle g(x)=\frac{1 - \sqrt{1-4f(x)}}{2f(x)}=\frac{2}{1+\sqrt{1-4f(x)}} g(x)=2f(x)114f(x) =1+14f(x) 2。多项式开根加求逆即可。

复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<bitset>
#include<sstream>
#include<ctime>
//#include<chrono>
//#include<random>
//#include<unordered_map>
using namespace std;

#define ll long long
#define ls o<<1
#define rs o<<1|1
#define pii pair<int,int>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
const double pi=acos(-1.0);
const double eps=1e-6;
// const int mod=998244353;
const int INF=0x3f3f3f3f;
const int maxn=2e5+5;	
ll read(){
	ll x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
namespace Ploynomial{  
    const ll mod=998244353,G=3;  
    int rev[maxn<<1];  
    ll qpow(ll a,ll p=mod-2){  
        ll res=1;  
        while(p){  
            if(p&1)res=res*a%mod;  
            a=a*a%mod;  
            p>>=1;  
        }  
        return res;  
    }  
    ll invG=qpow(G);  
    void print(ll *f,int n){  
        for(int i=0;i<n;i++)printf("%lld ",f[i]);  
        puts("");  
    }  
    void Rev(ll *f,int n){  
        for(int i=0;i<n;i++){  
            rev[i]=(rev[i>>1]>>1)|((i&1)?n>>1:0);  
            if(i<rev[i])swap(f[i],f[rev[i]]);  
        }  
    }  
    ll fix(ll x){  
        return (x%mod+mod)%mod;  
    }  
    void NTT(ll *f,int n,int flag){//1:DFT -1:IDFT  
        Rev(f,n);  
        for(int i=1;i<n;i<<=1){  
            ll wn=qpow(flag>0?G:invG,(mod-1)/(2*i));  
            for(int j=0;j<n;j+=2*i){  
                ll w=1;  
                for(int k=0;k<i;k++){  
                    ll tmp=w*f[j+k+i]%mod;  
                    f[j+k+i]=fix(f[j+k]-tmp);  
                    f[j+k]=fix(f[j+k]+tmp);  
                    w=w*wn%mod;  
                }  
            }  
        }  
        if(flag<0){  
            ll invn=qpow(n);  
            for(int i=0;i<n;i++)  
                f[i]=f[i]*invn%mod;  
        }  
    }  
    //加法卷积  
    ll f1[maxn<<1],g1[maxn<<1];  
    void Mul(ll *f,int n,ll *g,int m,ll *h){  
        int N;  
        for(N=1;N<n+m-1;N<<=1);  
        copy(f,f+n,f1);  
        fill(f1+n,f1+N,0);  
        copy(g,g+m,g1);  
        fill(g1+m,g1+N,0);  
        NTT(f1,N,1);  
        NTT(g1,N,1);  
        for(int i=0;i<N;i++)h[i]=f1[i]*g1[i]%mod;  
        NTT(h,N,-1);  
        fill(h+n+m-1,h+N,0);  
        fill(f1,f1+N,0);  
        fill(g1,g1+N,0);  
    }  
    //多项式求逆 g!=f  
    ll inv_t[maxn<<1];  
    void Inv(ll *f,int n,ll *g){  
        int N;  
        for(N=1;N<n;N<<=1);  
        fill(g,g+N,0);  
        g[0]=qpow(f[0]);  
        for(int i=2;i<=N;i<<=1){  
            copy(f,f+i,inv_t);  
            fill(inv_t+i,inv_t+(i<<1),0);  
            NTT(g,i<<1,1);  
            NTT(inv_t,i<<1,1);  
            for(int j=0;j<(i<<1);j++)  
                g[j]=fix(2*g[j]-g[j]*g[j]%mod*inv_t[j]%mod);  
            NTT(g,i<<1,-1);  
            fill(g+i,g+(i<<1),0);  
        }  
        fill(g+n,g+N,0);  
        fill(inv_t,inv_t+N,0);  
    }  
    //求导  
    int Deriv(ll *f,int n,ll *h){  
        for(int i=1;i<n;i++){  
            h[i-1]=f[i]*i%mod;  
        }  
        return n-1;  
    }  
    //积分  
    ll inv[maxn];  
    int Integ(ll *f,int n,ll *h){  
        inv[1]=1;  
        for(int i=2;i<=n;i++){  
            inv[i]=inv[mod%i]*(mod-mod/i)%mod;  
        }  
        for(int i=n;i>=1;i--){  
            h[i]=f[i-1]*inv[i]%mod;  
        }  
        h[0]=0;  
        return n+1;  
    }  
    //多项式开根 g!=f  
    ll sqrt_t[maxn<<1],sqrt_t2[maxn<<1];  
    void Sqrt(ll *f,int n,ll *g){  
        int N;  
        for(N=1;N<n;N<<=1);  
        fill(g,g+N,0);  
        g[0]=1;  
        ll inv2=qpow(2);  
        for(int i=2;i<=N;i<<=1){  
            Inv(g,i,sqrt_t);  
            NTT(sqrt_t,i<<1,1);  
            NTT(g,i<<1,1);  
            copy(f,f+i,sqrt_t2);  
            fill(sqrt_t2+i,sqrt_t2+(i<<1),0);  
            NTT(sqrt_t2,i<<1,1);  
            for(int j=0;j<(i<<1);j++){  
                g[j]=(g[j]*g[j]%mod+sqrt_t2[j])%mod*inv2%mod*sqrt_t[j]%mod;  
            }  
            NTT(g,i<<1,-1);  
            fill(g+i,g+(i<<1),0);  
        }  
        fill(g+n,g+N,0);  
    }  
    //多项式除法,已知f,g,deg(f)=n,deg(g)=m求q,r,deg(q)=n-m,deg(r)<m,满足f=g*q+r  
    void Reverse(ll *f,int n){  
        for(int i=0;i<n/2;i++)swap(f[i],f[n-1-i]);  
    }  
    ll div_t[maxn<<1];  
    void Div(ll *f,int n,ll *g,int m,ll *q,ll *r){  
        Reverse(f,n);  
        Reverse(g,m);  
        Inv(g,n-m+1,div_t);  
        Mul(f,n,div_t,n-m+1,q);  
        Reverse(q,n-m+1);  
        Reverse(g,m);  
        Reverse(f,n);  
        Mul(g,m,q,n-m+1,div_t);  
        for(int i=0;i<m-1;i++)r[i]=fix(f[i]-div_t[i]);  
    }  
      
    //多项式ln  
    ll ln_t[maxn<<1],ln_t2[maxn<<1];  
    void Ln(ll *f,int n,ll *h){  
        Inv(f,n,ln_t);  
        Deriv(f,n,ln_t2);  
        Mul(ln_t,n,ln_t2,n-1,h);  
        fill(h+n-1,h+2*n,0);  
        // for(int i=n-1;i<2*n-1;i++)h[i]=0;  
        Integ(h,n-1,h);  
        fill(ln_t,ln_t+n,0);  
        fill(ln_t2,ln_t2+n,0);  
    }  
  
    //多项式exp g!=f  
    ll exp_t[maxn<<1];  
    void Exp(ll *f,int n,ll *g){  
        int N;  
        for(N=1;N<n;N<<=1);  
        fill(g,g+N,0);  
        g[0]=1;  
        for(int i=2;i<=N;i<<=1){  
            Ln(g,i,exp_t);  
            for(int j=0;j<i;j++){  
                exp_t[j]=fix(f[j]-exp_t[j]);  
            }  
            exp_t[0]=(exp_t[0]+1)%mod;  
            Mul(exp_t,i,g,i,g);  
            fill(g+i,g+2*i,0);  
        }  
        fill(g+n,g+N,0);  
        fill(exp_t,exp_t+N,0);  
    }  
  
    //多项式快速幂,k可以很大,先用mod取模  
    ll pow_t[maxn<<1];  
    void Pow(ll *f,int n,ll k,ll *g){  
        Ln(f,n,pow_t);  
        for(int i=0;i<n;i++)pow_t[i]=pow_t[i]*k%mod;  
        Exp(pow_t,n,g);  
    }  
}  
using namespace Ploynomial;  
int n,m,len=100001;
int c[maxn];
ll f[maxn<<1],invg[maxn<<1],g[maxn<<1];
int main(void){
	// freopen("in.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&c[i]);
		f[c[i]]=1;
	}
	for(int i=0;i<len;i++){
		f[i]*=-4;
	}
	f[0]++;
	Sqrt(f,len,g);
	g[0]++;
	Inv(g,len,invg);
	for(int i=0;i<len;i++)invg[i]=invg[i]*2%mod;
	for(int i=1;i<=m;i++){
		printf("%lld\n",invg[i]);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值