BZOJ 3625: [Codeforces Round #250]小朋友和二叉树 dp 生成函数 多项式开根

3625: [Codeforces Round #250]小朋友和二叉树

Time Limit: 40 Sec  Memory Limit: 256 MB
Submit: 743  Solved: 336
[Submit][Status][Discuss]

Description

我们的小朋友很喜欢计算机科学,而且尤其喜欢二叉树。
考虑一个含有n个互异正整数的序列c[1],c[2],...,c[n]。如果一棵带点权的有根二叉树满足其所有顶点的权值都在集合{c[1],c[2],...,c[n]}中,我们的小朋友就会将其称作神犇的。并且他认为,一棵带点权的树的权值,是其所有顶点权值的总和。
给出一个整数m,你能对于任意的s(1<=s<=m)计算出权值为s的神犇二叉树的个数吗?请参照样例以更好的理解什么样的两棵二叉树会被视为不同的。
我们只需要知道答案关于998244353(7*17*2^23+1,一个质数)取模后的值。

Input

第一行有2个整数 n,m(1<=n<=10^5; 1<=m<=10^5)。
第二行有n个用空格隔开的互异的整数 c[1],c[2],...,c[n](1<=c[i]<=10^5)。

Output

输出m行,每行有一个整数。第i行应当含有权值恰为i的神犇二叉树的总数。请输出答案关于998244353(=7*17*2^23+1,一个质数)取模后的结果。

Sample Input

样例一:
2 3
1 2
样例二:
3 10
9 4 3
样例三:
5 10
13 10 6 4 15

Sample Output

样例一:
1
3
9
样例二:
0
0
1
1
0
2
4
2
6
15
样例三:
0
0
0
1
0
1
0
2
0
5

HINT

对于第一个样例,有9个权值恰好为3的神犇二叉树:


设 f[i] 为权值为 i 的树的个数

则枚举根的价值可得


之后 如果我们令 g(i) 表示 i 是否属于 A 我们就可以用分治fft啦!

但是BJ的目的是学习多项式开根 QwQ


令 f 的生成函数为 F j是否可行的生成函数为A

考虑过程 枚举左右子树与该根 但是不要忘记空树 所以+1


由于多项式求逆要有常数项

A没有常数项 若取减号就没常数了 所以只能取加号


之后多项式开根作为工具就可以施展拳脚了

和多项式求逆的化法一样


#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

typedef double db;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar(x%10+'0');}

const int N=300100,mod=998244353;

int inv_2;

inline int qpow(int x,int y)
{
	int res(1);
	while(y)
	{
		if(y&1) res=1ll*res*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return res;
}

int R[N];

void ntt(int *x,int lim,int opt)
{
	register int i,j,k,m,g,gn,tmp;
	for(i=0;i<lim;++i)
	{
		R[i]=(i&1)*(lim>>1)+(R[i>>1]>>1);
		if(R[i]<i) swap(x[i],x[R[i]]);
	}
	for(m=2;m<=lim;m<<=1)
	{
		k=m>>1;
		gn=qpow(3,(mod-1)/m);
		for(i=0;i<lim;i+=m)
		{
			g=1;
			for(j=0;j<k;++j,g=1ll*g*gn%mod)
			{
				tmp=1ll*x[i+j+k]*g%mod;
				x[i+j+k]=(x[i+j]-tmp+mod)%mod;
				x[i+j]=(x[i+j]+tmp)%mod;
			}
		}
	}
	if(opt==-1)
	{
		reverse(x+1,x+lim);
		tmp=qpow(lim,mod-2);
		for(i=0;i<lim;++i) x[i]=1ll*x[i]*tmp%mod;
	}
}

int tmp[N];

// (2-ab)*b

void get_inv(int *a,int *b,int deg)
{
	if(deg==1)
	{b[0]=qpow(a[0],mod-2);return ;}
	get_inv(a,b,(deg>>1)+(deg&1));
	int lim=1;
	while(lim<(deg<<1)) lim<<=1;
	memcpy(tmp,a,sizeof(int)*deg);
	memset(tmp+deg,0,sizeof(int)*(lim-deg));
	ntt(tmp,lim,1);ntt(b,lim,1);
	for(int i=0;i<lim;++i)
		b[i]=(1ll*b[i]*(2-1ll*tmp[i]*b[i]%mod)%mod+mod)%mod;
	ntt(b,lim,-1);
	memset(b+deg,0,sizeof(int)*(lim-deg));
}

// (b^2+a)/2b

void get_root(int *a,int *b,int *inv,int deg)
{
	if(deg==1)
	{b[0]=1;return ;}
	get_root(a,b,inv,(deg>>1)+(deg&1));
	memset(inv,0,sizeof(int)*deg);
	get_inv(b,inv,deg);
	int lim=1;
	while(lim<(deg<<1)) lim<<=1;
	memcpy(tmp,a,sizeof(int)*deg);
	memset(tmp+deg,0,sizeof(int)*(lim-deg));
	ntt(tmp,lim,1);ntt(b,lim,1);ntt(inv,lim,1);
	for(int i=0;i<lim;++i)
		b[i]=1ll*(b[i]+1ll*tmp[i]*inv[i]%mod)*inv_2%mod;
	ntt(b,lim,-1);
	memset(b+deg,0,sizeof(int)*(lim-deg));
}

int A[N],B[N],C[N];

int main()
{
	int n=read(),m=read();
	register int i,x;
	A[0]=1;
	for(i=1;i<=n;++i) x=read(),A[x]=mod-4;
	inv_2=qpow(2,mod-2);
	x=1;while(x<=m) x<<=1;
	get_root(A,B,C,x);
	
	for(i=0;i<=m;++i) A[i]=B[i];A[0]++;
	memset(B,0,sizeof(B));
	get_inv(A,B,x);
	
	for(i=1;i<=m;++i)
		print((B[i]<<1)%mod),putchar('\n');
	return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值