1 条题解
-
0
D1. 乌龟与 MEX 问题(简单版本) — 详细题解
一、问题重述
给定 个序列,第 个序列为 。
$$x \leftarrow \operatorname{mex}(x, a_{i,1}, a_{i,2}, \dots, a_{i,l_i}) $$
初始有一个非负整数 。每次操作可以选择一个序列 ,并将 更新为:其中 定义为最小的未出现在集合中的非负整数。
操作可以进行任意多次(包括零次),同一个序列可以被重复选择。定义 为从初始值 出发,通过最优操作能达到的 最大 值。
给定 ,需要计算:
二、单个序列的性质
2.1 定义关键值
对于第 个序列 ,定义两个重要值:
- 第一 MEX:,即 中缺失的最小非负整数。
- 第二 MEX:,即 加上 后缺失的最小非负整数。
注意: 也可以描述为 大于 的第一个不在 中的整数。
2.2 单次操作的效果
对序列 执行一次操作,设当前值为 :
情况 1:
此时 恰好是 缺失的最小值。加上 后, 被填补,因此新的 mex 变为下一个缺失值:
情况 2:
此时 仍然不在 中(因为 ),且所有小于 的数都在 中。因此最小缺失值仍是 :
总结(核心性质):
$$\operatorname{mex}(x, S_i) = \begin{cases} q_i, & \text{if } x = p_i \\[4pt] p_i, & \text{if } x \neq p_i \end{cases} $$
三、可达值分析
3.1 从单个序列能到达的值
从任意 出发,对序列 重复操作:
- 若 ,一次操作后 变为 。
- 若 ,一次操作后 变为 。
- 若 ,由于 ,一次操作后 变回 。
因此, 和 之间可以来回切换。从任意 出发,最多两步就能到达 (先到 ,再到 )。
3.2 多个序列的协同
考虑所有 个序列,设:
定理:从任意 出发,可以到达 。
证明:
- 取达到 的那个序列 ,即 。
- 若当前 ,一步到 。
- 若当前 ,一步到 ,再一步到 。
- 若当前 不是 且无法直接到 ?实际上任何 都可以通过合适的序列调整到 吗?需要更细致的论证,但结论成立。
定理:从任意 出发,无法到达大于 的数。
证明:
- 任何操作的结果要么是某个 (),要么是某个 ()。
- 因此操作只会得到不超过 的值。
- 由于 ,最优策略是 不进行任何操作,保留 本身。
因此:
$$f(k) = \begin{cases} M, & \text{if } k \le M \\[4pt] k, & \text{if } k > M \end{cases} $$其中 。
四、求和公式
设 。根据 与 的大小关系,分两种情况计算 。
情况 1:
对所有 ,有 。
共有 项,因此:情况 2:
- 当 时,,共 项,贡献 。
- 当 时,,这是一个等差数列:
- 首项
- 末项
- 项数
等差数列求和公式:
$$\sum_{k=M+1}^m k = \frac{(M+1 + m) \cdot (m - M)}{2} $$总答案为:
$$\sum_{k=0}^m f(k) = (M+1) \cdot M + \frac{(M+1 + m)(m - M)}{2} $$
五、算法步骤
- 对每个序列 :
- 将 个元素存入集合(或排序后去重)。
- 计算 :从 开始找第一个不在集合中的整数。
- 计算 :从 开始找第一个不在集合中的整数。
- 计算 。
- 根据 与 的关系,用上述公式计算答案。
时间复杂度:,满足 的限制。
六、代码实现(标程)
#include <bits/stdc++.h> using namespace std; void solve() { int n; long long m; cin >> n >> m; int M = 0; // 存储所有 q_i 的最大值 for (int i = 0; i < n; i++) { int l; cin >> l; set<int> st; for (int j = 0; j < l; j++) { int x; cin >> x; st.insert(x); } // 计算 p_i(第一个缺失) int p = 0; while (st.count(p)) p++; // 计算 q_i(大于 p 的第一个缺失) int q = p + 1; while (st.count(q)) q++; M = max(M, q); } long long ans = 0; if (m <= M) { ans = (m + 1) * M; } else { ans = (long long)(M + 1) * M; // 0..M 部分 ans += (M + 1 + m) * (m - M) / 2; // M+1..m 部分 } cout << ans << "\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
七、样例验证
样例 1
序列:
- :,
- :,
- :,
,,情况 2:
$$\text{ans} = (3+1) \times 3 + \frac{(3+1+4)(4-3)}{2} = 4 \times 3 + \frac{8 \times 1}{2} = 12 + 4 = 16 $$与样例输出一致 ✅
样例 2
序列:
- :去重后 ,,
- :,,
- :,,
,,情况 1:
与样例输出一致 ✅
样例 4
序列:
- :,
- :,
,,情况 1:
与样例输出一致 ✅
八、注意事项
- 使用
long long存储答案,因为 可达 ,求和可能超过 位整数范围。 - 可能远大于 ,但寻找 和 时只需检查到 即可,为简单起见直接用
set存储所有值。 - 输入数据量大,建议使用
ios::sync_with_stdio(false)和cin.tie(nullptr)加速。
- 1
信息
- ID
- 7004
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 4
- 标签
- 递交数
- 2
- 已通过
- 1
- 上传者