1 条题解
-
0
题意
给定一个长度为 的数组 (下标从 到 )。
每次操作同时将所有元素替换为:重复操作直到数组全为零。求最少操作次数,若永远不能全零则输出 。
关键观察
. 操作的本质
定义变换 :
下标模 ,所有 同时更新。
这是一个线性变换(在 上),且具有很好的结构。
. 分治性质
由于 是 的幂,数组可以不断对半分割。
设当前数组长度为 ( 是 的幂)。
$$L = [a_0, a_1, \dots, a_{len/2-1}], \quad R = [a_{len/2}, a_{len/2+1}, \dots, a_{len-1}] $$
考虑将其分成前后两半:进行一次操作后,观察新数组的前半部分:
$$b_i = a_i \oplus a_{i+len/2} \quad (0 \le i < len/2) $$这正是 和 的对应位置异或。
. 决策分支
-
如果 (即 对所有 成立),则 ,此时后半部分在操作后变为 (因为 $a_{i+len/2} \oplus a_{i+len/2+1} = a_i \oplus a_{i+1}$,恰好与前半部分相同)。实际上,此时问题可以缩小一半:只需对 继续操作即可,因为 会与 同步变化。
-
如果 ,则 非零,并且可以证明,经过 次操作后,数组会变为 的形式(后半全零)。此时问题转化为对长度为 的数组 继续操作,且已经花费了 步。
. 递归/迭代算法
. 若当前数组全为零,返回 。 . 若 :因为 ,一次操作即可,返回 。 . 否则:
- 计算 ()。
- 如果所有 (即 ):
- 则只需递归处理长度为 的 。
- 否则:
- 花费 步,然后递归处理长度为 的 。 . 最后若 ,还需额外一步(对应 的情况)。
. 正确性说明
- 当 时,操作 次后数组会变为全零?不对,实际上每次操作会同时更新 和 ,但由于 ,它们始终保持相等,且每次操作相当于对 自身做一次变换(因为 )。因此, 独立演化, 作为其副本。所以问题规模减半,且无需额外步数。
- 当 时,经过 次操作后,后半部分变为全零,前半部分变为 。这 步是必须花费的。
- 最终当长度为 时,若该元素非零,则再花 步使其变为零。
示例验证
样例
- :,相等 → 长度减半为 ,
- :,不等 → ,步数 ,
- : → 步数 总步数 ✅
样例
全零 → 输出 ✅
样例
→ 输出 ✅
样例
模拟可得步数 ✅
复杂度
每次循环长度减半,总操作 , 可过。
完整代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } // 检查是否全零 bool allZero = true; for (int x : a) { if (x != 0) { allZero = false; break; } } if (allZero) { cout << "0\n"; return 0; } // 特判 n=1 if (n == 1) { cout << "1\n"; return 0; } int steps = 0; int len = n; while (len > 1) { vector<int> b(len / 2); bool equal = true; for (int i = 0; i < len / 2; i++) { b[i] = a[i] ^ a[i + len / 2]; if (b[i] != 0) equal = false; } if (equal) { // 两半相等,缩小问题 a.resize(len / 2); len /= 2; } else { // 需要一次完整操作 steps += len / 2; a = move(b); len /= 2; } } // 最后还剩一个元素 if (a[0] != 0) steps++; cout << steps << "\n"; return 0; } -
- 1
信息
- ID
- 7195
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者