[codeforces438E]The Child and Binary Tree
[codeforces438E]The Child and Binary Tree
试题描述
Our child likes computer science very much, especially he likes binary trees.
Consider the sequence of n distinct positive integers: \(c_1, c_2, \cdots , c_n\). The child calls a vertex-weighted rooted binary tree good if and only if for every vertex v, the weight of v is in the set \(\{c_1, c_2, \cdots , c_n\}\). Also our child thinks that the weight of a vertex-weighted tree is the sum of all vertices' weights.
Given an integer \(m\), can you for all \(s (1 \le s \le m)\) calculate the number of good vertex-weighted rooted binary trees with weight \(s\)? Please, check the samples for better understanding what trees are considered different.
We only want to know the answer modulo \(998244353\) (\(7 \times 17 \times 223 + 1\), a prime number).
给出 \(n\) 种点的点权,定义一棵二叉树的权值等于它所有点的点权和。求对于 \([1, m]\) 中的 \(s\),权值为 \(s\) 的不同的二叉树有多少种。两棵二叉树不同当且仅当它们的左子树、右子树或根节点点权不同。一棵二叉树中可以出现多个点权相同的点。
输入
The first line contains two integers \(n, m (1 \le n \le 10^5; 1 \le m \le 10^5)\). The second line contains n space-separated pairwise distinct integers \(c_1, c_2, ..., c_n\). \((1 \le c_i \le 10^5)\).
输出
Print \(m\) lines, each line containing a single integer. The \(i\)-th line must contain the number of good vertex-weighted rooted binary trees whose weight exactly equal to \(i\). Print the answers modulo \(998244353\) (\(7 \times 17 \times 2^{23} + 1\), a prime number).
输入示例
3 10
9 4 3
输出示例
0
0
1
1
0
2
4
2
6
15
数据规模及约定
见“输入”
题解
首先看看暴力 dp 怎么解决这个问题。设 \(f_k\) 表示权值为 \(k\) 的二叉树的数目,那么有转移方程(注意 dp 边界):
f_0 = 1
\]
然后搞生成函数,令 \(C(x) = \sum_{i=1}^n { x^{c_i} }\),\(F(x) = \sum_{i=0}^{+ \infty} { f_i \cdot x^i }\)。
然后我们发现里面的 sigma 是一个卷积,然后把式子缩一点:
[x^0]F(x) = 1
\]
然后前面那个 \(1\),由于只在幂是 \(c_i\) 的时候出现,可以想象 \(C(x)\) 又在和 \(F^2(x)\) 做卷积,即
[x^0]F(x) = 1
\]
然后我们发现可以化成初中学过的二元一次方程的形式:
\]
用 \(x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\) 求根公式解一下上面这个关于 \(F(x)\) 的方程,得到
\]
两个解,怎么办呢?
初中老师告诉我们:检验!
怎么检验?我们从 \([x^0]F(x) = 1\) 入手,可以发现这就是在 \(x = 0\) 的时候,\(F(x) = 1\)。
但是由于 \(C(x)\) 常数项为 \(0\),且它在分母,所以显然有
\]
所以可以排除这个解了,但为了严谨,我们当然还要验证一下另一个解,但是另一个解的检验比较棘手,因为我们会得到一个 \(0\) 除以 \(0\) 的形式,这时候就需要用洛必达法则了(\(\leftarrow\) 戳它进入百度百科)
= \lim_{x \rightarrow 0} \frac{1 - \sqrt{1-4x}}{2x} \\\\
= \lim_{x \rightarrow 0} \frac{\frac{\mathrm{d}(1 - \sqrt{1-4x})}{\mathrm{d}x}}{\frac{\mathrm{d}(2x)}{\mathrm{d}x}} \\\\
= 1
\]
(以上直接跳过求导过程,读者不妨仔细手算一下)正确了!
那么接下来搞一个多项式求逆、开方\(^*\)就好啦。
注意,上面的式子不能直接算,因为 \(C(x)\) 常数项为 \(0\),不存在逆元!不过没关系,我们可以分子有理化一下:
= \frac{1 - (1 - 4C(x))}{2C(x) (1 + \sqrt{1 - 4C(x)})} \\
= \frac{2}{1 + \sqrt{1 - 4C(x)}}
\]
这样就可以直接求逆元啦!
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)
const int BufferSize = 1 << 16;
char buffer[BufferSize], *Head, *Tail;
inline char Getchar() {
if(Head == Tail) {
int l = fread(buffer, 1, BufferSize, stdin);
Tail = (Head = buffer) + l;
}
return *Head++;
}
int read() {
int x = 0, f = 1; char c = Getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
return x * f;
}
#define maxn 524288
#define MOD 998244353
#define Groot 3
#define LL long long
int Pow(int a, int b) {
int ans = 1, t = a;
while(b) {
if(b & 1) ans = (LL)ans * t % MOD;
t = (LL)t * t % MOD; b >>= 1;
}
return ans;
}
int brev[maxn];
void FFT(int *a, int len, int tp) {
int n = 1 << len;
rep(i, 0, n - 1) if(i < brev[i]) swap(a[i], a[brev[i]]);
rep(i, 1, len) {
int wn = Pow(Groot, MOD - 1 >> i);
if(tp < 0) wn = Pow(wn, MOD - 2);
for(int j = 0; j < n; j += 1 << i) {
int w = 1;
rep(k, 0, (1 << i >> 1) - 1) {
int la = a[j+k], ra = (LL)w * a[j+k+(1<<i>>1)] % MOD;
a[j+k] = (la + ra) % MOD;
a[j+k+(1<<i>>1)] = (la - ra + MOD) % MOD;
w = (LL)w * wn % MOD;
}
}
}
if(tp < 0) rep(i, 0, n - 1) a[i] = (LL)a[i] * Pow(n, MOD - 2) % MOD;
return ;
}
void Mul(int *A, int an, int *B, int bn) {
int n = an + bn, N = 1, len = 0;
while(N <= n) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len - 1);
FFT(A, len, 1); FFT(B, len, 1);
rep(i, 0, N - 1) A[i] = (LL)A[i] * B[i] % MOD;
FFT(A, len, -1);
return ;
}
int tmp[maxn];
void inverse(int *f, int *g, int n) { // module x^n
if(n == 1) return (void)(f[0] = Pow(g[0], MOD - 2));
inverse(f, g, n + 1 >> 1);
int N = 1, len = 0;
while(N <= (n << 2)) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len - 1);
rep(i, n + 1 >> 1, N - 1) f[i] = 0; rep(i, 0, n - 1) tmp[i] = g[i]; rep(i, n, N - 1) tmp[i] = 0;
FFT(f, len, 1); FFT(tmp, len, 1);
rep(i, 0, N - 1) f[i] = (2ll - (LL)tmp[i] * f[i] % MOD + MOD) * f[i] % MOD;
FFT(f, len, -1);
rep(i, n, N - 1) f[i] = 0;
return ;
}
int inv[maxn], _inv[maxn];
void p_sqrt(int *f, int *g, int n) { // g[0] = 1
if(n == 1) return (void)(f[0] = 1);
p_sqrt(f, g, n + 1 >> 1);
rep(i, 0, (n + 1 >> 1) - 1) _inv[i] = (f[i] << 1) % MOD; rep(i, n + 1 >> 1, n - 1) _inv[i] = 0;
inverse(inv, _inv, n);
int N = 1, len = 0;
while(N <= n + 1) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len - 1);
rep(i, n + 1 >> 1, N - 1) f[i] = 0; rep(i, 0, n - 1) tmp[i] = g[i]; rep(i, n, N - 1) tmp[i] = 0;
FFT(f, len, 1); FFT(tmp, len, 1);
rep(i, 0, N - 1) f[i] = (tmp[i] + (LL)f[i] * f[i]) % MOD;
FFT(f, len, -1);
rep(i, n, N - 1) f[i] = 0;
Mul(f, n - 1, inv, n - 1);
N = 1; while(N <= (n - 1 << 1)) N <<= 1;
rep(i, n, N - 1) f[i] = 0;
return ;
}
int num[50], cntn;
void putint(int x) {
if(!x) return (void)puts("0");
cntn = 0;
while(x) num[++cntn] = x % 10, x /= 10;
dwn(i, cntn, 1) putchar(num[i] + '0'); putchar('\n');
return ;
}
int n, val[maxn], C[maxn], c[maxn];
int main() {
int n = read(), m = read(), mxv = 0;
rep(i, 1, n) val[i] = read(), mxv = max(mxv, val[i]);
rep(i, 1, n) if(val[i] <= m) C[val[i]]++;
rep(i, 1, m) C[i] = MOD - 4ll * C[i] % MOD; C[0] = 1;
p_sqrt(c, C, m + 1); (c[0] += 1) %= MOD;
inverse(C, c, m + 1);
rep(i, 0, m) (C[i] <<= 1) %= MOD;
rep(i, 1, m) putint(C[i]);
return 0;
}
\(^*\)多项式开方,可以用前文(请翻到最后一题)的方法,但要用到多项式求 ln 和 exp,很麻烦,常数也大。这里我们可以利用一下开根的次数为 \(2\) 这个特殊性质优化一下。(以下多项式都省略后面的 \((x)\))
还是考虑倍增,令 \(f_0^2 \equiv g (\mathrm{mod}\ x^{\lceil \frac{n}{2} \rceil})\),用 \(f_0, g\) 表示出 \(f^2 \equiv g (\mathrm{mod}\ x^n)\) 的 \(f\)。
显然有
\]
两边平方一下就好啦
f^2 - 2f_0f + f_0^2 \equiv 0 (\mathrm{mod}\ x^n) \\\\
g - 2f_0f + f_0^2 \equiv 0 (\mathrm{mod}\ x^n)
\]
于是得到
\]
[codeforces438E]The Child and Binary Tree的更多相关文章
- Codeforces 250 E. The Child and Binary Tree [多项式开根 生成函数]
CF Round250 E. The Child and Binary Tree 题意:n种权值集合C, 求点权值和为1...m的二叉树的个数, 形态不同的二叉树不同. 也就是说:不带标号,孩子有序 ...
- 【CF438E】The Child and Binary Tree(多项式运算,生成函数)
[CF438E]The Child and Binary Tree(多项式运算,生成函数) 题面 有一个大小为\(n\)的集合\(S\) 问所有点权都在集合中,并且点权之和分别为\([0,m]\)的二 ...
- [题解] CF438E The Child and Binary Tree
CF438E The Child and Binary Tree Description 给一个大小为\(n\)的序列\(C\),保证\(C\)中每个元素各不相同,现在你要统计点权全在\(C\)中,且 ...
- 【CF】438E. The Child and Binary Tree
http://codeforces.com/contest/438/problem/E 题意:询问每个点权值在 $c_1, c_2, ..., c_m$ 中,总权值和为 $s$ 的二叉树个数.请给出每 ...
- Codeforces 438E. The Child and Binary Tree 多项式,FFT
原文链接www.cnblogs.com/zhouzhendong/p/CF438E.html 前言 没做过多项式题,来一道入门题试试刀. 题解 设 $a_i$ 表示节点权值和为 $i$ 的二叉树个数, ...
- CF438E The Child and Binary Tree 生成函数、多项式开根
传送门 设生成函数\(C(x) = \sum\limits_{i=0}^\infty [\exists c_j = i]x^i\),答案数组为\(f_1 , f_2 , ..., f_m\),\(F( ...
- cf438E. The Child and Binary Tree(生成函数 多项式开根 多项式求逆)
题意 链接 Sol 生成函数博大精深Orz 我们设\(f(i)\)表示权值为\(i\)的二叉树数量,转移的时候可以枚举一下根节点 \(f(n) = \sum_{w \in C_1 \dots C_n} ...
- Codeforces 438E The Child and Binary Tree [DP,生成函数,NTT]
洛谷 Codeforces 思路 看到计数和\(998244353\),可以感觉到这是一个DP+生成函数+NTT的题. 设\(s_i\)表示\(i\)是否在集合中,\(A\)为\(s\)的生成函数,即 ...
- Codeforces 438E The Child and Binary Tree - 生成函数 - 多项式
题目传送门 传送点I 传送点II 传送点III 题目大意 每个点的权值$c\in {c_{1}, c_{2}, \cdots, c_{n}}$,问对于每个$1\leqslant s\leqslant ...
随机推荐
- vue项目中的函数封装
项目中一般都会有fun.js这类的文件,里面有各种的如转换时间格式的,处理转换的等等函数方法. 其实经常用到的去获取基本数据的接口也可以封装成一个方法,方便复用. 如上面所标,获取列表数据之前需要先获 ...
- JS实现数组去重的方法(6种)
方法一: 双层循环,外层循环元素,内层循环时比较值 如果有相同的值则跳过,不相同则push进数组 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Arra ...
- Python分布式爬虫抓取知乎用户信息并进行数据分析
在以前的文章中,我写过一篇使用selenium来模拟登录知乎的文章,然后在很长一段时间里都没有然后了... 不过在最近,我突然觉得,既然已经模拟登录到了知乎了,为什么不继续玩玩呢?所以就创了一个项目, ...
- JRE和JDK区别
JRE: Java Runtime Environment JDK:Java Development Kit JRE顾名思义是java运行时环境, 包含了java虚拟机,java基础类库. 是使用ja ...
- qt5.10.1编译记录
qt版本更新比较快,不知道选哪个版本合适,故选择一个较新版本的. 平台imx6 + linux4.1.16 + qt5.10.1 采用明远智睿提供的编译器:fsl-imx-fb-g ...
- [CodeForces238E]Meeting Her(图论+记忆化搜索)
Description 题目链接:Codeforces Solution 因为路线随机,所以找出各路线最短路必须经过的点,在这个点必定能上车 直接floyd暴力找割点 然后不断用k条公交车路线来更新D ...
- Android 人脸识别
Android人脸识别技术,可以参考下面的网站. http://www.faceplusplus.com.cn/ 本项目使用的就是该网站的api. 项目具体使用的技术代码 /** * 用来压缩图片的方 ...
- python 10月30日复习
1.把一个数字的list从小到大排序,然后写入文件,然后从文件中读取出来文件内容,然后反序,在追加到文件的下一行中 import codecs list1 = [2,23,8,54,86,12] li ...
- centos使用--开机启动
centos6.8 1.利用 chkconfig 来配置启动级别 在CentOS或者RedHat其他系统下,如果是后面安装的服务,如httpd.mysqld.postfix等,安装后系统默认不会自动启 ...
- 容器技术的落地还要依靠SDN
容器能够实现新应用程序的快速部署,代表着目前IT开发社区的最热门趋势之一.然而,想要实现容器部署生产环境,IT人员还需要使用SDN技术,在分布式微应用程序之间实现可扩展.可管理且安全的通信. 什么是容 ...