atcoder ABC439 题解

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. 1 ≤ i, j, k ≤ N
  2. Aᵢ : Aⱼ : Aₖ = 7 : 5 : 3(即三个元素的比值为 7:5:3)
  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(Nlog⁡N)O(N \log N)O(NlogN),遍历过程中每次二分查找的复杂度为 O(log⁡L)O(\log L)O(logL),其中 LLL 是当前最长上升子序列的长度,最大不超过 NNN,因此整体时间复杂度为 O(Nlog⁡N)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,质数)取模,核心思路与步骤如下:

  1. 从后往前遍历:用 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] 对应的合法贡献存入树状数组,为左侧元素的统计提供支撑。

  1. 从左往右遍历:用 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] 的合法贡献。

  1. 用 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(log⁡N)O(\log N)O(logN),快速幂计算逆元和 222 的幂次的时间复杂度为 O(log⁡N)O(\log N)O(logN)。算法中存在两次遍历排列(从后往前和从左往右),每次遍历中的核心操作均为 O(log⁡N)O(\log N)O(logN),因此整体时间复杂度为 O(Nlog⁡N)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 个人将使用这个棋盘和骰子进行游戏,游戏规则如下:

  1. 初始时,将 LLL 个棋子(棋子 111、棋子 222、...\dots...、棋子 LLL)全部放置在格子 000 上。

  2. 回合从第 111 个人开始,按照编号顺序轮流进行。严格来说,第 iii 个人的回合结束后,回合将交给第 (i mod L)+1(i \bmod L) + 1(imodL)+1 个人。

  3. 每个人在自己的回合中执行以下操作:

    设当前回合的玩家编号为 iii,先掷骰子;再设棋子 iii 当前所在的格子为 xxx,骰子掷出的数字为 yyy,将棋子 iii 移动到格子 min⁡(N,x+y)\min(N, x+y)min(N,x+y)(即若 x+yx+yx+y 超过 NNN,则直接停在格子 NNN 上)。

  4. 第一个将自己编号对应的棋子移动到格子 NNN 上的玩家获胜,其余玩家均失败。

对于 i=1,2,...,Li=1,2,\dots,Li=1,2,...,L,请分别求出第 iii 个玩家获胜的概率,答案对 998244353998244353998244353 取模。

补充说明:

关于「概率对 998244353 取模」 由于概率通常是分数形式,在模意义下需将分数转化为整数进行计算:

  1. 设概率为最简分数 pq\frac{p}{q}qp(p,qp, qp,q 为整数,q≠0q \neq 0q=0)。
  2. 因为 998244353998244353998244353 是质数,先计算 qqq 关于 998244353998244353998244353 的乘法逆元 q−1q^{-1}q−1(即 q998244353−2mod  998244353q^{998244353-2} \mod 998244353q998244353−2mod998244353)。
  3. 最终结果为 (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;
}

这种科技题我是真的再也不想写了,前置内容太多了)

相关推荐
程序员zgh1 天前
类AI技巧 —— 文字描述+draw.io 自动生成图表
c语言·c++·ai作画·流程图·ai编程·甘特图·draw.io
这周也會开心1 天前
JVM-垃圾回收算法
jvm·算法
学编程就要猛1 天前
算法:4.长度最小的子数组
算法
红豆诗人1 天前
算法和数据结构--时间复杂度和空间复杂度
数据结构·算法
阿豪只会阿巴1 天前
【多喝热水系列】从零开始的ROS2之旅——Day5
c++·笔记·python·ubuntu·ros2
郑泰科技1 天前
fmm(快速地图匹配)实践:Boost header not found解决方案
c++·windows·交通物流
维C泡泡1 天前
STL(初识string)
开发语言·c++
郝学胜-神的一滴1 天前
Linux线程使用注意事项:骈文技术指南
linux·服务器·开发语言·数据结构·c++·程序人生
高山上有一只小老虎1 天前
小红的字符串
java·算法