atcoder abc439 题解
先祝大家新年快乐!!!
A - 2^n - 2*n
题目描述
给你一个整数 NNN ,计算并输出 2N−2×N2^N - 2 \times N2N−2×N 的值(注: 2N2^N2N 表示 2 的 N 次方, 2×N2 \times N2×N 表示 2 乘以 N)。
约束条件
NNN 是一个介于 1 和 11(包含 1 和 11)之间的整数。
输入格式
输入数据从标准输入读取,格式如下:
N
输出格式
输出计算结果(仅一行)。
解题思路
可以使用 pow 函数计算指数,因为 N 非常小,所以不会爆 int,不需要开long long
代码
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n;
cin >> n;
cout << pow(2, n) - 2 * n;
return 0;
}
B - Happy Number
题目描述
给你一个正整数 NNN,判断 NNN 是否为快乐数。
快乐数是指一个非负整数,经过有限次以下操作后能变为 1:
将该数替换为其十进制表示中各位数字的平方和。
例如,对 2026 执行一次该操作后,结果为 22+02+22+62=4+0+4+36=442^2 + 0^2 + 2^2 + 6^2 = 4 + 0 + 4 + 36 = 4422+02+22+62=4+0+4+36=44。
关于快乐数的更多示例,可参考样例输入输出的说明。
约束条件
NNN 是一个介于 1 和 2026(包含 1 和 2026)之间的整数。
输入格式
输入数据从标准输入读取,格式如下:
N
输出格式
如果 NNN 是快乐数,输出 Yes;否则,输出 No。
解题思路
我们使用 %10 和 /10 循环的操作来对 N 处理,每当我们变化成新的数字的时候,我们就用一个 vis 数组记录一下:vis[i] 表示 i 这个结果已经出现过了。
当我们重复遇到某一个数字的时候,说明出现了死循环,直接输出 No 就好,否则输出 Yes。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e3 + 5;
int f[maxn];
int main()
{
int n;
cin >> n;
f[n] = 1;
while (n != 1)
{
int nxt = 0;
while (n)
{
nxt += (n % 10) * (n % 10);
n /= 10;
}
if (f[nxt])
{
cout << "No";
return 0;
}
f[nxt] = 1;
n = nxt;
}
cout << "Yes";
return 0;
}
C - 2026
题目描述
一个正整数 n 满足以下条件时,被称为"好整数":
存在恰好一对整数 (x,y),使得 0 < x < y 且 x² + y² = n。
例如,当 n=2026 时,可以验证 (x,y)=(1,45) 是唯一满足 0 < x < y 且 x² + y² = n 的整数对。因此,2026 是一个好整数。
给你一个正整数 N,枚举所有不超过 N 的好整数。
约束条件
1 ≤ N ≤ 10⁷
N 是整数。
输入格式
输入数据从标准输入读取,格式如下:
N
输出格式
设不超过 N 的好整数有 k 个,将这些整数按升序排列得到序列 (a₁, a₂, ..., aₖ)。按以下格式输出答案。(若 k=0,则第二行输出空行。)
k
a₁ a₂ ... aₖ
解题思路
首先用一个 O(n\sqrt{n}n ) 的方法把所有小于 n 的平方数全都处理出来,然后双重循环枚举这些平方数,统计所有合法的 n 的数量,具体看代码。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 5;
int f[maxn]; // 每一个 n 出现的次数
int main()
{
vector<int> s, ans;
int n;
cin >> n;
for (int i = 1; i * i <= n; i++)
s.push_back(i * i); // 把所有的平方数处理出来
for (int i = 0; i < s.size(); i++)
for (int j = i + 1; j < s.size(); j++) // 枚举 x^2 y^2
if (s[i] + s[j] <= n)
f[s[i] + s[j]]++; // 出现的次数加一
for (int i = 1; i <= n; i++)
if (f[i] == 1) // 如果只出现了一次,说明是正确答案
ans.push_back(i);
cout << ans.size() << '\n';
for (int i = 0; i < ans.size(); i++)
cout << ans[i] << ' ';
return 0;
}
D - Kadomatsu Subsequence
题目描述
给你一个长度为 N 的整数序列 A = (A₁, A₂, ..., Aₙ)。
请找出满足以下所有条件的整数三元组 (i, j, k) 的数量:
- 1 ≤ i, j, k ≤ N
- Aᵢ : Aⱼ : Aₖ = 7 : 5 : 3(即三个元素的比值为 7:5:3)
- min(i, j, k) = j 或 max(i, j, k) = j(即 j 是 i、j、k 三个下标中的最小值或最大值)
约束条件
所有输入值均为整数。
1 ≤ N ≤ 3×10⁵
1 ≤ Aᵢ ≤ 10⁹
输入格式
输入数据从标准输入读取,格式如下:
N
A₁ A₂ ... Aₙ(一行,N 个整数,用空格分隔)
输出格式
输出答案。
解题思路
min(i, j, k) = j 或 max(i, j, k) = j 的处理方式是一样的,这里我们只以 min(i, j, k) = j 举例,我们对 A 数组进行一次从右往左枚举 j,此时满足条件的 AiA_iAi 、AkA_kAk 都在 j 的右边(已经在枚举的时候被扫过了),我们用一个 map 维护所有 AiA_iAi、AkA_kAk 出现的次数,当确定了 AjA_jAj 之后,我们就可以按照比例确定 AiA_iAi 和 AkA_kAk 的值,让他们出现的次数相乘就是本次枚举的结果,计入最终答案即可。
代码
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int maxn = 1e7 + 5;
unordered_map<int, int> m1, m2; // 记录每个元素出现的次数
int n, a[maxn];
int ans;
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) // j 最大的情况
{
m1[a[i]]++;
if (a[i] % 5 != 0)
continue;
int num_i = m1[a[i] / 5 * 7], num_k = m1[a[i] / 5 * 3]; // 确定 A_i A_k
ans += num_i * num_k; // 相乘便是本次枚举的答案
}
for (int i = n; i >= 1; i--)
{
m2[a[i]]++;
if (a[i] % 5 != 0)
continue;
int num_i = m2[a[i] / 5 * 7], num_k = m2[a[i] / 5 * 3];
ans += num_i * num_k;
}
cout << ans;
return 0;
}
E - Kite
题目描述
新年快乐!说到新年的户外活动,当然是放风筝啦!
编号为 1 到 N 的 N 个人正在河边放风筝。
河岸对面的河流呈直线流淌,因此我们建立一个二维坐标系:x 轴为河流方向,y 轴为高度方向。
第 i 个人站在点 (Aᵢ, 0) 处,试图让风筝飞到点 (Bᵢ, 1) 处。
然而,为了避免人员和风筝碰撞,以及风筝线缠绕,若满足以下条件,第 i 个人和第 j 个人(i ≠ j)不能同时放风筝:
连接 (Aᵢ, 0) 和 (Bᵢ, 1) 的线段,与连接 (Aⱼ, 0) 和 (Bⱼ, 1) 的线段存在交点(包括线段端点重合的情况)。
在遵守上述约束的前提下,最多有多少人可以同时放风筝?
约束条件
1 ≤ N ≤ 2×10⁵
0 ≤ Aᵢ ≤ 10⁹
0 ≤ Bᵢ ≤ 10⁹
所有输入值均为整数。
输入格式
输入数据从标准输入读取,格式如下:
N
A₁ B₁
A₂ B₂
⋮
Aₙ Bₙ
输出格式
输出最多可以同时放风筝的人数。
解题思路
本题的关键在于将"风筝线不相交"的约束条件转化为易于求解的序列问题。对于两个放风筝的人对应的 (A1,B1)(A_1,B_1)(A1,B1) 和 (A2,B2)(A_2,B_2)(A2,B2),两人不会产生冲突的等价条件是 A1<A2A_1 < A_2A1<A2 且 B1<B2B_1 < B_2B1<B2,或者 A1>A2A_1 > A_2A1>A2 且 B1>B2B_1 > B_2B1>B2。基于这个等价条件,我们可以先对所有风筝的 (A,B)(A,B)(A,B) 对进行排序,排序规则为按 AAA 从小到大排列,若 AAA 值相同则按 BBB 从大到小排列。这样排序的目的是处理 AAA 值相同的特殊情况,优先处理 BBB 值更大的风筝时,不会和后续同 AAA 但 BBB 更小的风筝产生冲突,也不会将其错误计入答案。排序完成后,原问题就转化为求排序后 BBB 序列的最长上升子序列长度,这个长度就是最多可同时放风筝的人数。
我们定义 kite 结构体来存储每个风筝的 AAA 和 BBB 值,并重载小于运算符以实现上述的排序规则。读取输入的 nnn 和所有风筝的 (A,B)(A,B)(A,B) 数据后,对结构体数组进行排序。接下来用数组 fff 来维护最长上升子序列的最小末尾元素,其中 f[i]f[i]f[i] 表示选中的第 iii 个风筝对应的 BBB 值最小值。遍历排序后的风筝序列时,对于当前风筝的 BBB 值,用二分查找寻找 fff 数组中最大的 iii 使得 f[i]f[i]f[i] 大于等于当前 BBB 值。如果没有找到这样的元素,说明当前 BBB 值可以延长最长上升子序列,直接将其加入 fff 数组末尾;如果找到,则用当前 BBB 值替换该位置的元素,以此保证 fff 数组的"最小末尾"特性,为后续元素构造更长的子序列留出空间。最终 fff 数组的长度就是所求的答案。
排序的时间复杂度为 O(NlogN)O(N \log N)O(NlogN),遍历过程中每次二分查找的复杂度为 O(logL)O(\log L)O(logL),其中 LLL 是当前最长上升子序列的长度,最大不超过 NNN,因此整体时间复杂度为 O(NlogN)O(N \log N)O(NlogN),可以处理 N≤2×105N \le 2 \times 10^5N≤2×105 的数据规模。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int n, f[maxn];
struct kite
{
int a, b;
bool operator<(const kite &tmp) const // 按照刚才的方法排序
{
if (a == tmp.a)
return b > tmp.b;
return a < tmp.a;
}
} k[maxn];
int main()
{
cin >> n;
int len = 0;
for (int i = 1; i <= n; i++)
cin >> k[i].a >> k[i].b;
sort(k + 1, k + n + 1);
for (int i = 1; i <= n; i++)
{
if (k[i].b > f[len]) // 二分找不到比 k[i].b 小的 f[i],直接加到最后面
f[++len] = k[i].b;
else
{
// 二分找到当前风筝可以插入的位置
int idx = lower_bound(f + 1, f + len + 1, k[i].b) - f;
f[idx] = k[i].b;
}
}
cout << len;
return 0;
}
F - Beautiful Kadomatsu
题目描述
一个长度为 kkk 的序列 a=(a1,a2,...,ak)a=(a_1,a_2,\dots,a_k)a=(a1,a2,...,ak) 被定义为类似门松序列,定义方式如下:
设 xxx 为满足以下条件的整数 iii 的数量: 2≤i≤k−12 \le i \le k-12≤i≤k−1 且 ai−1<aia_{i-1} < a_iai−1<ai 且 ai>ai+1a_i > a_{i+1}ai>ai+1。 设 yyy 为满足以下条件的整数 iii 的数量: 2≤i≤k−12 \le i \le k-12≤i≤k−1 且 ai−1>aia_{i-1} > a_iai−1>ai 且 ai<ai+1a_i < a_{i+1}ai<ai+1。 当且仅当 x>yx > yx>y 时,序列 aaa 被称为类似门松序列 。 给定一个 (1,2,...,N)(1,2,\dots,N)(1,2,...,N) 的排列 PPP,请计算 PPP 的所有(不一定连续的)子序列中,是类似门松序列的数量,答案对 998244353998244353998244353 取模。
约束条件
所有输入值均为整数。
1≤N≤3×1051 \le N \le 3 \times 10^51≤N≤3×105
PPP 是 (1,2,...,N)(1,2,\dots,N)(1,2,...,N) 的一个排列。
输入格式
输入数据从标准输入读取,格式如下:
N
P_1 P_2 ... P_N
输出格式
输出答案。
解题思路
感觉比上一个题简单。
本题看似需要统计满足 x>yx>yx>y 的「类似门松序列」,但通过手动模拟简单案例(比如短长度子序列)可以发现一个关键的简化结论:一个子序列是合法的「类似门松序列」,当且仅当它满足两个条件------第一个元素小于第二个元素,且最后一个元素小于倒数第二个元素 。 我们采用三个树状数组 t1t1t1、t2t2t2、t3t3t3 来分阶段高效统计合法子序列数量,答案对 998244353998244353998244353(下文记为 MODMODMOD,质数)取模,核心思路与步骤如下:
- 从后往前遍历:用 t2t2t2 维护 r[i]r[i]r[i] 统计右侧可能性
我们定义 r[i]r[i]r[i]:当第 iii 个元素作为子序列的「倒数第二个元素」时,其右侧满足条件(即作为「最后一个元素」,且值小于第 iii 个元素)的可能性数量(本质是右侧所有小于 a[i]a[i]a[i] 的元素对应的合法组合数之和)。
遍历过程中,借助树状数组 t2t2t2 维护右侧元素的前缀和信息,通过查询 a[i]−1a[i]-1a[i]−1 的前缀和,即可快速得到 r[i]r[i]r[i](无需暴力枚举右侧元素),随后更新 t2t2t2,将当前元素 a[i]a[i]a[i] 对应的合法贡献存入树状数组,为左侧元素的统计提供支撑。
- 从左往右遍历:用 t1t1t1 维护 l[i]l[i]l[i] 统计左侧可能性
同理,我们定义 l[i]l[i]l[i]:当第 iii 个元素作为子序列的「第二个元素」时,其左侧满足条件(即作为「第一个元素」,且值小于第 iii 个元素)的可能性数量(本质是左侧所有小于 a[i]a[i]a[i] 的元素对应的合法组合数之和)。
遍历过程中,借助树状数组 t1t1t1 维护左侧元素的前缀和信息,通过查询 a[i]−1a[i]-1a[i]−1 的前缀和,即可快速得到 l[i]l[i]l[i],随后更新 t1t1t1,存储当前元素 a[i]a[i]a[i] 的合法贡献。
- 用 t3t3t3 维护倒数第二个元素前面的可能性
前面仅统计了「第一个/第二个元素」和「倒数第二个/最后一个元素」的可能性,还需考虑第二个元素和倒数第二个元素中间的元素 :假设在原序列中,这两个元素之间有 kkk 个元素,每个元素都有「选」或「不选」两种选择,因此中间共有 2k2^k2k 种组合可能性。
为了高效计算这种跨区间的组合贡献,我们用树状数组 t3t3t3 维护 2−(i+1)×l[i]2^{-(i+1)} \times l[i]2−(i+1)×l[i](其中 2−(i+1)2^{-(i+1)}2−(i+1) 是模意义下的逆元,由费马小定理转化为 quick_pow(2,(i+1)×(MOD−2),MOD)\text{quick\_pow}(2, (i+1) \times (MOD-2), MOD)quick_pow(2,(i+1)×(MOD−2),MOD),目的是后续拼接时抵消多余的幂次,简化计算)。
当我们枚举到第 iii 个元素的 r[i]r[i]r[i] 时,若要计算子序列长度大于3的合法情况,只需先通过 t3t3t3 查询前 i−1i-1i−1 个元素的累计和 ∑j=1i−1l[j]×2−(j+1)\sum_{j=1}^{i - 1}l[j] \times 2^{-(j+1)}∑j=1i−1l[j]×2−(j+1),再将该累计和与 2i×r[i]2^i \times r[i]2i×r[i] 相乘,即可得到中间元素组合后的合法贡献。
此外,还需叠加子序列长度为 3 的简单情况(即 l[i]×r[i]l[i] \times r[i]l[i]×r[i]),最终将所有元素的贡献累加,即为总答案。
树状数组的单点更新和前缀和查询的时间复杂度均为 O(logN)O(\log N)O(logN),快速幂计算逆元和 222 的幂次的时间复杂度为 O(logN)O(\log N)O(logN)。算法中存在两次遍历排列(从后往前和从左往右),每次遍历中的核心操作均为 O(logN)O(\log N)O(logN),因此整体时间复杂度为 O(NlogN)O(N \log N)O(NlogN),能够高效应对 N≤3×105N \le 3 \times 10^5N≤3×105 的数据规模约束,不会出现超时问题。
代码
cpp
#include <bits/stdc++.h>
#define lowbit(i) i & -i
#define int long long
using namespace std;
const int MOD = 998244353, maxn = 3e5 + 5;
int n, a[maxn], t1[maxn], t2[maxn], t3[maxn], ans, r[maxn];
void update(int x, int k, int *t) // 树状数组更新
{
for (int i = x; i <= n; i += lowbit(i))
t[i] += k, t[i] %= MOD;
}
int query(int x, int *t) // 前缀和查询
{
int ans = 0;
for (int i = x; i; i -= lowbit(i))
ans += t[i], ans %= MOD;
return ans;
}
int quick_pow(int a, int b) // 快速幂
{
int ans = 1;
while (b)
{
if (b % 2 == 1)
ans *= a, ans %= MOD;
a = a * a, a %= MOD, b /= 2;
}
return ans;
}
signed main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = n; i >= 1; i--) // 先把 r 算出来
{
r[i] = query(a[i] - 1, t2);
update(a[i], 1, t2);
}
for (int i = 1; i <= n; i++) // 然后 t1 和 t3 一块处理
{
int num = query(a[i] - 1, t1); // 相当于 l[i]
update(a[i], 1, t1); // 维护 l
update(i, num * quick_pow(quick_pow(2, (i + 1)), MOD - 2) % MOD, t3);
int num1 = query(i - 1, t3);
ans += (r[i] * quick_pow(2, i) % MOD * num1 % MOD + num * r[i] % MOD) % MOD, ans %= MOD; // k > 3 和 k = 3 的情况
}
cout << ans;
return 0;
}
G - Sugoroku 6
题目描述
新年快乐!说到新年的室内活动,当然是双六棋(Sugoroku)啦! 有一个由 N+1N+1N+1 个格子组成的双六棋棋盘,格子编号为 0,1,...,N0, 1, \dots, N0,1,...,N。
还有一个 MMM 面的骰子,掷出每个正整数 A1,A2,...,AMA_1, A_2, \dots, A_MA1,A2,...,AM 的概率相等,且 A1,A2,...,AMA_1, A_2, \dots, A_MA1,A2,...,AM 互不相同。 编号为 1,2,...,L1, 2, \dots, L1,2,...,L 的 LLL 个人将使用这个棋盘和骰子进行游戏,游戏规则如下:
-
初始时,将 LLL 个棋子(棋子 111、棋子 222、...\dots...、棋子 LLL)全部放置在格子 000 上。
-
回合从第 111 个人开始,按照编号顺序轮流进行。严格来说,第 iii 个人的回合结束后,回合将交给第 (i mod L)+1(i \bmod L) + 1(imodL)+1 个人。
-
每个人在自己的回合中执行以下操作:
设当前回合的玩家编号为 iii,先掷骰子;再设棋子 iii 当前所在的格子为 xxx,骰子掷出的数字为 yyy,将棋子 iii 移动到格子 min(N,x+y)\min(N, x+y)min(N,x+y)(即若 x+yx+yx+y 超过 NNN,则直接停在格子 NNN 上)。
-
第一个将自己编号对应的棋子移动到格子 NNN 上的玩家获胜,其余玩家均失败。
对于 i=1,2,...,Li=1,2,\dots,Li=1,2,...,L,请分别求出第 iii 个玩家获胜的概率,答案对 998244353998244353998244353 取模。
补充说明:
关于「概率对 998244353 取模」 由于概率通常是分数形式,在模意义下需将分数转化为整数进行计算:
- 设概率为最简分数 pq\frac{p}{q}qp(p,qp, qp,q 为整数,q≠0q \neq 0q=0)。
- 因为 998244353998244353998244353 是质数,先计算 qqq 关于 998244353998244353998244353 的乘法逆元 q−1q^{-1}q−1(即 q998244353−2mod 998244353q^{998244353-2} \mod 998244353q998244353−2mod998244353)。
- 最终结果为 (p×q−1)mod 998244353(p \times q^{-1}) \mod 998244353(p×q−1)mod998244353。
约束条件
所有输入值均为整数。
1≤N≤2.5×1051 \le N \le 2.5 \times 10^51≤N≤2.5×105
1≤M≤N1 \le M \le N1≤M≤N
2≤L≤2.5×1052 \le L \le 2.5 \times 10^52≤L≤2.5×105
1≤A1<A2<⋯<AM≤N1 \le A_1 < A_2 < \dots < A_M \le N1≤A1<A2<⋯<AM≤N
输入格式
输入数据从标准输入读取,格式如下:
N M L
A_1 A_2 A_3 ...
输出格式
输出 LLL 行,第 iii 行输出第 iii 个玩家获胜的概率(对 998244353998244353998244353 取模后的结果)。
解题思路
说实话,我自己没想出思路来[\doge],还是看了题解 + 好几个小时代码才出来,这里简单说一说(怕说的不好),这种科技题还是要先掌握前置知识才行。
需要大家先去学:
分治NTT、求逆、bostan-mori......
先求解单个玩家从初始格子 0 到达各格子 i 的概率 p [i]。这里定义 p [i] 为单个玩家最终能到达格子 i(未超过终点 N)的概率,基于骰子移动规则构建递推关系,通过生成函数将递推转化为多项式运算,再结合 Bostan-Mori 算法(分式线性递推快速求值)与 calc 函数的修正处理,求解出 p [i] 序列。
随后,将单个玩家的概率转化为多玩家竞争场景下的贡献。定义 q [i] 为某玩家在格子 i 时,直接一步获胜且此前所有玩家均未获胜的概率贡献,其表达式为 (p[i]−p[i+1])×p[i](L−1)mod998244353(p [i] - p [i+1]) × p [i]^(L-1) mod 998244353(p[i]−p[i+1])×p[i](L−1)mod998244353,其中 (p [i] - p [i+1]) 是单个玩家从 i 一步获胜的概率,p [i]^(L-1) 是其余 L-1 个玩家均无法从 i 一步获胜的修正项;同时定义 s [i] 为从格子 i 转移到 i+1(未获胜)的概率转移系数,即 p [i+1] × p [i]^(-1) mod 998244353,表征未获胜时的转移概率比例。
最后,通过分治合并处理多个格子的贡献与转移关系。多个格子的 q [i](贡献)与 s [i](转移)构成分式线性递推,递归合并区间 [l, r] 的结果,得到每个区间的分子多项式(获胜概率贡献和)与分母多项式(转移概率乘积和)。设左区间的分子、分母分别为 Lfi、Lse,右区间的分子、分母分别为 Rfi、Rse,则合并时的多项式关系为:new_fi=Lfi×Rse+Lse×Rfi,new_se=Lse×Rse,其中乘法均为模 998244353 下的多项式乘法。合并得到全局的分子 P 与分母 Q 后,求解 Q 的多项式逆元,通过多项式乘法得到 R=P×Q−1,R 序列的前 L 项即为 L 个玩家的获胜概率,依次输出即可。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
bool Mst;
bool Med;
const int maxn = 1048580;
const int MOD = 998244353;
int lim, rev[maxn];
long long ilim, w[maxn];
long long fac[maxn], ifac[maxn];
int n, m, L;
int a[maxn];
long long p[maxn], q[maxn], s[maxn];
inline long long add(long long x, long long y)
{
return (x += y) >= MOD && (x -= MOD), x;
}
inline long long Add(long long &x, long long y)
{
return x = add(x, y);
}
inline long long sub(long long x, long long y)
{
return (x -= y) < 0 && (x += MOD), x;
}
inline long long Sub(long long &x, long long y)
{
return x = sub(x, y);
}
inline long long qpow(long long a, long long b)
{
long long res = 1;
for (; b; b >>= 1, a = a * a % MOD)
if (b & 1)
res = res * a % MOD;
return res;
}
inline void init_factorial(int n)
{
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i - 1] * i % MOD;
ifac[n] = qpow(fac[n], MOD - 2);
for (int i = n; i >= 1; i--)
ifac[i - 1] = ifac[i] * i % MOD;
}
inline void init_NTT(int n)
{
lim = 1;
while (lim < n)
lim <<= 1;
ilim = qpow(lim, MOD - 2);
for (int i = 1; i < lim; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * (lim >> 1));
for (int i = 1; i < lim; i <<= 1)
{
long long wn = qpow(3, (MOD - 1) / (i << 1)), cur = 1;
for (int j = 0; j < i; j++, cur = cur * wn % MOD)
w[i | j] = cur;
}
}
inline void NTT(long long *F, int type)
{
for (int i = 0; i < lim; i++)
if (i < rev[i])
swap(F[i], F[rev[i]]);
for (int i = 1; i < lim; i <<= 1)
for (int j = 0; j < lim; j += i << 1)
for (int k = 0; k < i; k++)
{
long long x = F[j | k], y = w[i | k] * F[i | j | k] % MOD;
F[j | k] = add(x, y), F[i | j | k] = sub(x, y);
}
if (type == 1)
return;
reverse(F + 1, F + lim);
for (int i = 0; i < lim; i++)
F[i] = F[i] * ilim % MOD;
}
using poly = vector<long long>;
inline poly operator+(const poly &f, const poly &g)
{
poly h(max(f.size(), g.size()), 0);
for (int i = 0; i < (int)f.size(); i++)
Add(h[i], f[i]);
for (int i = 0; i < (int)g.size(); i++)
Add(h[i], g[i]);
return h;
}
inline poly operator-(const poly &f, const poly &g)
{
poly h(max(f.size(), g.size()), 0);
for (int i = 0; i < (int)f.size(); i++)
Add(h[i], f[i]);
for (int i = 0; i < (int)g.size(); i++)
Sub(h[i], g[i]);
return h;
}
inline poly operator*(const poly &f, const poly &g)
{
if (!f.size())
return g;
if (!g.size())
return f;
poly h(f.size() + g.size() - 1);
init_NTT(h.size());
static long long F[1048580], G[1048580];
for (int i = 0; i < lim; i++)
F[i] = G[i] = 0;
for (int i = 0; i < (int)f.size(); i++)
F[i] = f[i];
for (int i = 0; i < (int)g.size(); i++)
G[i] = g[i];
NTT(F, 1), NTT(G, 1);
for (int i = 0; i < lim; i++)
F[i] = F[i] * G[i] % MOD;
NTT(F, -1);
for (int i = 0; i < (int)h.size(); i++)
h[i] = F[i];
return h;
}
inline poly poly_inv(const poly &f)
{
if ((int)f.size() == 1)
return poly{qpow(f[0], MOD - 2)};
int n = f.size(), m = (n + 1) >> 1;
poly g(f);
g.resize(m);
g = poly_inv(g);
static long long F[1048580], G[1048580];
init_NTT(f.size() << 1);
for (int i = 0; i < lim; i++)
F[i] = G[i] = 0;
for (int i = 0; i < (int)f.size(); i++)
F[i] = f[i];
for (int i = 0; i < (int)g.size(); i++)
G[i] = g[i];
NTT(F, 1), NTT(G, 1);
for (int i = 0; i < lim; i++)
G[i] = G[i] * sub(2, F[i] * G[i] % MOD) % MOD;
NTT(G, -1);
g.resize(n);
for (int i = 0; i < n; i++)
g[i] = G[i];
return g;
}
inline vector<poly> operator*(const vector<poly> &f, const vector<poly> &g)
{
if (!f.size())
return g;
if (!g.size())
return f;
int fx = f.size(), fy = 0, gx = g.size(), gy = 0;
for (const auto &o : f)
fy = max(fy, (int)o.size());
for (const auto &o : g)
gy = max(gy, (int)o.size());
if (!fy)
return g;
if (!gy)
return f;
int hx = fx + gx - 1, hy = fy + gy - 1, lx = 1, ly = 1;
while (lx < hx)
lx <<= 1;
while (ly < hy)
ly <<= 1;
vector<poly> exf(lx, poly(ly, 0)), exg(lx, poly(ly, 0));
static long long F[1048580], G[1048580];
init_NTT(hy);
for (int i = 0; i < fx; i++)
{
for (int j = 0; j < ly; j++)
F[j] = 0;
for (int j = 0; j < (int)f[i].size(); j++)
F[j] = f[i][j];
NTT(F, 1);
for (int j = 0; j < ly; j++)
exf[i][j] = F[j];
}
for (int i = 0; i < gx; i++)
{
for (int j = 0; j < ly; j++)
G[j] = 0;
for (int j = 0; j < (int)g[i].size(); j++)
G[j] = g[i][j];
NTT(G, 1);
for (int j = 0; j < ly; j++)
exg[i][j] = G[j];
}
init_NTT(hx);
for (int i = 0; i < ly; i++)
{
for (int j = 0; j < lx; j++)
F[j] = exf[j][i];
NTT(F, 1);
for (int j = 0; j < lx; j++)
exf[j][i] = F[j];
}
for (int i = 0; i < ly; i++)
{
for (int j = 0; j < lx; j++)
G[j] = exg[j][i];
NTT(G, 1);
for (int j = 0; j < lx; j++)
exg[j][i] = G[j];
}
for (int i = 0; i < lx; i++)
for (int j = 0; j < ly; j++)
exf[i][j] = exf[i][j] * exg[i][j] % MOD;
init_NTT(hy);
for (int i = 0; i < lx; i++)
{
for (int j = 0; j < ly; j++)
F[j] = exf[i][j];
NTT(F, -1);
for (int j = 0; j < ly; j++)
exf[i][j] = F[j];
}
init_NTT(hx);
for (int i = 0; i < ly; i++)
{
for (int j = 0; j < lx; j++)
F[j] = exf[j][i];
NTT(F, -1);
for (int j = 0; j < lx; j++)
exf[j][i] = F[j];
}
exf.resize(hx);
for (auto &o : exf)
o.resize(hy);
return exf;
}
inline poly Bostan_Mori(const poly &f, const poly &g, int n)
{
vector<poly> U(f.size(), poly(1, 0)), V(g.size(), poly(2, 0)), _V;
for (int i = 0; i < (int)f.size(); i++)
U[i][0] = f[i];
for (int i = 0; i < (int)g.size(); i++)
V[i][1] = sub(0, g[i]);
V[0][0] = 1;
int m = n;
while (m)
{
_V = V;
for (int i = 1; i < (int)_V.size(); i += 2)
for (auto &o : _V[i])
o = sub(0, o);
U = U * _V, V = V * _V;
int new_V_len = (V.size() + 1) >> 1;
vector<poly> new_V(new_V_len);
for (int i = 0; i < new_V_len; i++)
if ((i << 1) < (int)V.size())
new_V[i] = V[i << 1];
V = move(new_V);
bool is_odd = m & 1;
int new_U_len = (U.size() + !is_odd) >> 1;
vector<poly> new_U(new_U_len);
for (int i = 0; i < new_U_len; i++)
{
int idx = (i << 1) | is_odd;
if (idx < (int)U.size())
new_U[i] = U[idx];
}
U = move(new_U);
m >>= 1;
U.resize(m + 1), V.resize(m + 1);
}
poly h = U[0] * poly_inv(V[0]);
h.resize(n + 1);
return h;
}
inline poly calc(const poly &f, const poly &g, int n)
{
long long c = g[0];
poly _g(g);
_g[0] = 0;
poly h = Bostan_Mori(f, _g, n), w(n + 1, 0);
for (int i = 0; i <= n; i++)
h[i] = h[i] * ifac[i] % MOD;
w[0] = 1;
for (int i = 1; i <= n; i++)
w[i] = w[i - 1] * c % MOD;
for (int i = 0; i <= n; i++)
w[i] = w[i] * ifac[i] % MOD;
h = h * w;
h.resize(n + 1);
for (int i = 0; i <= n; i++)
h[i] = h[i] * fac[i] % MOD;
return h;
}
inline pair<poly, poly> solve(int l, int r)
{
if (l == r)
return make_pair(poly{q[l]}, poly{1, sub(0, s[l])});
int mid = (l + r) >> 1;
auto L_res = solve(l, mid);
auto R_res = solve(mid + 1, r);
poly new_fi = L_res.first * R_res.second + L_res.second * R_res.first;
poly new_se = L_res.second * R_res.second;
return make_pair(new_fi, new_se);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> m >> L;
init_factorial(n);
for (int i = 1; i <= m; i++)
cin >> a[i];
poly f(n, 1);
poly g(n, 0);
long long inv_m = qpow(m, MOD - 2);
for (int i = 1; i <= m; i++)
if (a[i] < n)
g[a[i]] = inv_m;
poly h = calc(f, g, n - 1);
for (int i = 1; i <= n; i++)
p[i] = h[i - 1];
for (int i = 1; i <= n; i++)
q[i] = sub(p[i], p[i + 1]) * qpow(p[i], L - 1) % MOD;
for (int i = 1; i <= n; i++)
s[i] = p[i + 1] * qpow(p[i], MOD - 2) % MOD;
auto res = solve(1, n);
poly P = res.first;
poly Q = res.second;
Q.resize(L);
poly R = P * poly_inv(Q);
R.resize(L);
for (int i = 0; i < L; i++)
cout << R[i] << '\n';
return 0;
}
这种科技题我是真的再也不想写了,前置内容太多了)