题目大意

  考虑一个含有\(n\)个互异正整数的序列\(c_1,c_2,\ldots ,c_n\)。如果一棵带点权的有根二叉树满足其所有顶点的权值都在集合\(\{c_1,c_2,\ldots ,c_n\}\)中,我们的小朋友就会将其称作神犇的。并且他认为,一棵带点权的树的权值,是其所有顶点权值的总和。

  给出一个整数\(m\),你能对于任意的\(s(1\leq s\leq m)\)计算出权值为\(s\)的神犇二叉树的个数吗?

  我们只需要知道答案关于\(998244353\)取模后的值。

  \(n,m\leq 100000\)

题解

  设\(a_i\)为\(c\)中有没有\(i\)这个权值。

  设\(f_i\)为权值和为\(i\)的二叉树个数。

\[\begin{align}
f_0&=1\\
f_i&=\sum_{0\leq j\leq i}a_j\sum_{k=0}^{i-j}f_kf_{i-j-k}\\
\end{align}
\]

  设

\[\begin{align}
A(x)&=\sum_{i\geq 0}a_ix^i\\
F(x)&=\sum_{i\geq 0}f_ix^i
\end{align}
\]

  那么

\[\begin{align}
F(x)&=F(x)^2A(x)+1\\
A(x)F(x)^2-F(x)+1&=0\\
\Delta&=1-4A(x)\\
F(x)&=\frac{1\pm\sqrt{1-4A(x)}}{2A(x)}\\
&=\frac{2}{1\pm\sqrt{1-4A(x)}}
\end{align}
\]

  显然,当常数项有逆元的时候一个多项式才有逆元。而\(a_0=0\),所以只能取加号。

\[F(x)=\frac{2}{1+\sqrt{1-4A(x)}}
\]

  直接多项式开根+多项式求逆。

  时间复杂度:\(O(n\log n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
#include<cmath>
#include<functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
void sort(int &a,int &b)
{
if(a>b)
swap(a,b);
}
void open(const char *s)
{
#ifndef ONLINE_JUDGE
char str[100];
sprintf(str,"%s.in",s);
freopen(str,"r",stdin);
sprintf(str,"%s.out",s);
freopen(str,"w",stdout);
#endif
}
int rd()
{
int s=0,c;
while((c=getchar())<'0'||c>'9');
do
{
s=s*10+c-'0';
}
while((c=getchar())>='0'&&c<='9');
return s;
}
int upmin(int &a,int b)
{
if(b<a)
{
a=b;
return 1;
}
return 0;
}
int upmax(int &a,int b)
{
if(b>a)
{
a=b;
return 1;
}
return 0;
}
const ll p=998244353;
const ll g=3;
ll fp(ll a,ll b)
{
ll s=1;
while(b)
{
if(b&1)
s=s*a%p;
a=a*a%p;
b>>=1;
}
return s;
}
const int maxn=600000;
ll inv[maxn];
namespace ntt
{
ll w1[maxn];
ll w2[maxn];
int rev[maxn];
int n;
void init(int m)
{
n=1;
while(n<m)
n<<=1;
int i;
for(i=2;i<=n;i<<=1)
{
w1[i]=fp(g,(p-1)/i);
w2[i]=fp(w1[i],p-2);
}
rev[0]=0;
for(i=1;i<n;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
}
void ntt(int *a,int t)
{
int i,j,k;
int u,v,w,wn;
for(i=0;i<n;i++)
if(rev[i]<i)
swap(a[i],a[rev[i]]);
for(i=2;i<=n;i<<=1)
{
wn=(t==1?w1[i]:w2[i]);
for(j=0;j<n;j+=i)
{
w=1;
for(k=j;k<j+i/2;k++)
{
u=a[k];
v=1ll*a[k+i/2]*w%p;
a[k]=(u+v)%p;
a[k+i/2]=(u-v)%p;
w=1ll*w*wn%p;
}
}
}
if(t==-1)
{
u=fp(n,p-2);
for(i=0;i<n;i++)
a[i]=1ll*a[i]*u%p;
}
}
int x[maxn];
int y[maxn];
int z[maxn];
void copy_clear(int *a,int *b,int m)
{
int i;
for(i=0;i<m;i++)
a[i]=b[i];
for(i=m;i<n;i++)
a[i]=0;
}
void copy(int *a,int *b,int m)
{
int i;
for(i=0;i<m;i++)
a[i]=b[i];
}
void inverse(int *a,int *b,int m)
{
if(m==1)
{
b[0]=fp(a[0],p-2);
return;
}
inverse(a,b,m>>1);
init(m<<1);
copy_clear(x,a,m);
copy_clear(y,b,m>>1);
ntt(x,1);
ntt(y,1);
int i;
for(i=0;i<n;i++)
x[i]=y[i]*(2-1ll*x[i]*y[i]%p)%p;
ntt(x,-1);
copy(b,x,m);
}
int c[maxn],d[maxn];
void sqrt(int *a,int *b,int m)
{
if(m==1)
{
if(a[0]==1)
b[0]=1;
else if(a[0]==0)
b[0]=0;
else
//我也不会
;
return;
}
sqrt(a,b,m>>1);
// copy_clear(c,b,m>>1);
int i;
for(i=m;i<m<<1;i++)
b[i]=0;
inverse(b,d,m);
init(m<<1);
for(i=m;i<m<<1;i++)
b[i]=d[i]=0;
ll inv2=fp(2,p-2);
copy_clear(x,a,m);
ntt(x,1);
ntt(d,1);
for(i=0;i<n;i++)
x[i]=1ll*x[i]*d[i]%p;
ntt(x,-1);
for(i=0;i<m;i++)
b[i]=(1ll*(b[i]+x[i])%p*inv2)%p;
}
};
int a[maxn];
int b[maxn];
int main()
{
open("bzoj3625");
int n,m;
int i;
int x;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d",&x);
a[x]=1;
}
int k=1;
while(k<=m)
k<<=1;
for(i=0;i<k;i++)
a[i]=-4ll*a[i]%p;
a[0]=(a[0]+1)%p;
ntt::sqrt(a,b,k);
b[0]=(b[0]+1)%p;
ntt::inverse(b,a,k);
for(i=1;i<=m;i++)
{
a[i]=(a[i]*2ll%p+p)%p;
printf("%d\n",a[i]);
}
return 0;
}

【BZOJ3625】【CF438E】小朋友和二叉树 NTT 生成函数 多项式开根 多项式求逆的更多相关文章

  1. [BZOJ3625][CF438E]小朋友和二叉树 (多项式开根,求逆)

    题面 题解 设多项式的第a项为权值和为a的二叉树个数,多项式的第a项表示是否为真,即 则,所以F是三个多项式的卷积,其中包括自己: ,1是F的常数项,即. 我们发现这是一个一元二次方程,可以求出,因为 ...

  2. [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆)

    [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆) 题面 一棵二叉树的所有点的点权都是给定的集合中的一个数. 让你求出1到m中所有权 ...

  3. CF438E The Child and Binary Tree(生成函数+多项式开根+多项式求逆)

    传送门 可以……这很多项式开根模板……而且也完全不知道大佬们怎么把这题的式子推出来的…… 首先,这题需要多项式开根和多项式求逆.多项式求逆看这里->这里,这里讲一讲多项式开根 多项式开方:已知多 ...

  4. BZOJ3625 CF438E 小朋友与二叉树

    心态崩了 不放传送门了 辣鸡bz 还是正经一点写一下题解= = 就是显然我们可以把权值写成生成函数形式g(0/1序列)来表示权值是否出现 然后f来表示总的方案数 可以列出 分别枚举左右子树和空树的情况 ...

  5. [BZOJ3625][CF438E]小朋友和二叉树

    题面 Description 我们的小朋友很喜欢计算机科学,而且尤其喜欢二叉树. 考虑一个含有\(n\)个互异正整数的序列\(c_1,c_2,\ldots,c_n\).如果一棵带点权的有根二叉树满足其 ...

  6. BZOJ 3625:小朋友和二叉树 多项式开根+多项式求逆+生成函数

    生成函数这个东西太好用了~ code: #include <bits/stdc++.h> #define ll long long #define setIO(s) freopen(s&q ...

  7. cf438E. The Child and Binary Tree(生成函数 多项式开根 多项式求逆)

    题意 链接 Sol 生成函数博大精深Orz 我们设\(f(i)\)表示权值为\(i\)的二叉树数量,转移的时候可以枚举一下根节点 \(f(n) = \sum_{w \in C_1 \dots C_n} ...

  8. 洛谷P5205 【模板】多项式开根(多项式sqrt)

    题意 题目链接 Sol 这个就很没意思了 求个ln,然后系数除以2,然后exp回去. #include<bits/stdc++.h> #define Pair pair<int, i ...

  9. BZOJ 3625 [Codeforces Round #250]小朋友和二叉树 ——NTT 多项式求逆 多项式开根

    生成函数又有奇妙的性质. $F(x)=C(x)*F(x)*F(x)+1$ 然后大力解方程,得到一个带根号的式子. 多项式开根有解只与常数项有关. 发现两个解只有一个是成立的. 然后多项式开根.求逆. ...

随机推荐

  1. H5 68-伪元素选择器

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. Final Destination II -- 矩阵快速幂模板题

    求f[n]=f[n-1]+f[n-2]+f[n-3] 我们知道 f[n] f[n-1] f[n-2]         f[n-1]  f[n-2]  f[n-3]         1    1    ...

  3. SDN 实验室学生们

    SDN实验室系列:https://edu.cnblogs.com/campus/fzu/SdnLab --研究生及本科生们的博客及GitHub链接.欢迎各位大佬招募. 研究生 2018级 姓名 博客地 ...

  4. Python_守护进程、锁、信号量、事件、队列

    1.创建进程 守护进程(*****) _.daemon = True #  _进程成为守护进程 守护进程也是一个子进程. 主进程的<代码>执行结束之后守护进程自动结束. import ti ...

  5. Ubuntu端口开放

    一.关于iptable的介绍 维基百科:https://zh.wikipedia.org/wiki/Iptables 注意:iptables的操作需要root权限 二.具体操作 sudo apt-ge ...

  6. Docker常规防止容器自动退出

    [root@server-crm /]# docker attach songheng [root@fc0a891e1861 /]# cat /bin/auto_service.sh #!/bin/s ...

  7. [转帖]关于CPU Cache -- 程序猿需要知道的那些事

    关于CPU Cache -- 程序猿需要知道的那些事 很早之前读过作者的blog 记得作者在facebook 工作.. 还写过mysql相关的内容 大拿 本文将介绍一些作为程序猿或者IT从业者应该知道 ...

  8. day 7-3 僵尸进程,孤儿进程与守护进程

    一.基本定义 正常情况下,子进程是通过父进程创建的,子进程在创建新的进程.子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 当一个 进程完成它的工作终止之后,它 ...

  9. longquan

    /** * 登录后将数据填写到主数据 */ public void login(String login_nr) { //File f = new File(android.os.Environmen ...

  10. 【转】Java基础——容器分类

    Java容器可以说是增强程序员编程能力的基本工具,本系列将带您深入理解容器类. 容器的用途 如果对象的数量与生命周期都是固定的,自然我们也就不需要很复杂的数据结构. 我们可以通过创建引用来持有对象,如 ...