[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 ...
随机推荐
- textkit
更详细的内容可以参考官方文档 <Text Programming Guide for iOS>. “Text Kit指的是UIKit框架中用于提供高质量排版服务的一些类和协议,它让程序能够 ...
- 微信小程序清除默认样式
1.清除button的默认样式 button::after{border:none;}input{outline:none;border:none;list-style: none;}
- LOJ#515. 「LibreOJ β Round #2」贪心只能过样例(bitset)
内存限制:256 MiB时间限制:1000 ms标准输入输出 题目类型:传统评测方式:文本比较 上传者: nzhtl1477 提交提交记录统计讨论测试数据 题目描述 一共有 nnn个数,第 iii ...
- Dnsmasq域名解析系统安装配置
Dnsmasq使用上比bind要简便得多,可以做正向.反向dns解析,支持DHCP服务.也可以做内部dns服务器用. 默认下,dnsmasq使用系统的/etc/resolv.conf,并读取/etc/ ...
- 【CodeBase】【转】php随机生成汉字
本方法是通过生成GB2312编码的汉字后,再转码为UTF-8编码.之所以这样做是因为UTF-8的常用汉字太过分散,随机生成会出现大量生僻字,而使用GB2312编码的好处在于其收录的大部分汉字为常用汉字 ...
- emplace_back
c++11 的 list deque 和 vector 增加了emplace_back函数,相对于push_back函数,它减少了一次类的构造,因此效率更高,推荐使用. #include <li ...
- Pandas 基本技巧
1.数据查看和转置 import numpy as np import pandas as pd # 导入numpy.pandas模块 # 数据查看.转置 df = pd.DataFrame(np.r ...
- [Codeforces958E2]Guard Duty (medium)(区间DP)
Description 题目链接 Solution 可以把题目转化一下模型,将间隔取出来,转化为N-1个数,限制不能取相邻两个数,求取K个数的最小价值 设DP[i][j]表示前i个数取j个最大价值(第 ...
- dijkstra算法与优先队列
这是鄙人的第一篇技术博客,作为算法小菜鸟外加轻度写作障碍者,写技术博客也算是对自己的一种挑战和鞭策吧~ 言归正传,什么是dijkstra算法呢? -dijkstra算法是一种解决最短路径问题的简单有效 ...
- spark streaming的应用
今天我们讲spark streaming的应用,这个是实时处理的,类似于Storm以及Flink相关的知识点, 说来也巧,今天的自己也去听了关于Flink的相关的讲座,可惜自己没有听得特别清楚,好像是 ...