运算 - 题解
标签与难度
标签: 贪心, 数学, 思维题, 构造, 位运算 难度: 1400
题目大意喵~
你好呀,未来的算法大师!这道题是这样的喵~
我们有 个整数,从 到 。题目已经告诉我们 总是等于 0,真是个不错的起点呢!我们的任务是在每两个相邻的数 和 之间,从六种运算符号(加 +, 减 -, 除 /, 按位与 &, 按位或 |, 按位异或 ^)中挑选一个填进去。
运算规则很特别:从左到右依次计算,不考虑任何优先级。比如 a op1 b op2 c 的计算顺序是 (a op1 b) op2 c。
还有两个小提示:
- 位运算(
&,|,^)的对象是前一个结果和当前数字的绝对值。 - 除法是向零取整的整数除法。
我们的目标是,通过巧妙地选择这 个运算符,让最终的计算结果变得最大!你能帮我找到这个最大的结果吗?喵~
解题思路分析
刚看到这道题,可能会觉得有点头大,喵~ 每一步都有 6 种选择,总共有 步,那不就是 种可能性吗? 最大可是 呀,这要是暴力搜索,我的猫爪子都要算冒烟了也算不完!
所以,肯定有更聪明的办法!我们来一步一步分析,看看能不能发现什么规律,呐。
我们的计算过程是这样的: res_i = res_{i-1} (op_i) a_i 其中 res_i 是处理到第 个数字后的结果。我们的目标是最大化最终的 res_n。
这是一个典型的多阶段决策问题。一个自然的想法是:在每一步都做出最好的选择,是不是就能得到全局最好的结果呢?这种方法就是贪心。我们来验证一下这个想法是否正确。
第一步:从 到
我们的起点是 res_0 = a_0 = 0。现在要计算 res_1 = res_0 op_1 a_1 = 0 op_1 a_1。我们来把所有可能性都列出来看看:
0 + a_1 = a_10 - a_1 = -a_10 / a_1 = 0(假设 )0 & |a_1| = 00 | |a_1| = |a_1|0 ^ |a_1| = |a_1|
比较一下这些结果,a_1, -a_1, 0, |a_1|。哪一个最大呢?当然是 |a_1| 啦!所以,在第一步,我们可以通过选择 | 或者 ^ (或者根据 的符号选择 + 或 -),得到的最大结果是 res_1 = |a_1|。
最重要的一点是:我们得到的第一步的最大结果 |a_1| 是一个非负数!
第二步及以后:归纳与证明
现在我们进入了关键的一步。假设我们经过 步的精心选择,已经得到的当前最大结果 res_{i-1} 是一个非负数 C。现在我们要计算 res_i = C op_i a_i,我们应该选择哪个运算符才能让 res_i 最大呢?
因为 C 是非负的,我们来比较一下六种运算的结果:
加法 vs 减法:
C + a_i和C - a_i。如果a_i是正数,+好;如果a_i是负数,-好。综合起来,我们总能通过选择+或-得到C + |a_i|的结果。这比C - |a_i|要大得多!C + |a_i|vsC | |a_i|: 对于任意两个非负整数x和y,有一个非常重要的性质:x + y >= x | y。 为什么呢?可以从二进制的角度来理解。x | y是把x和y在二进制位上为 1 的地方都置为 1,不考虑进位。而x + y在这个基础上还处理了进位。每次进位都会让结果变得更大。所以,加法的结果总是大于等于按位或的结果。我们的C和|a_i|都是非负数,所以C + |a_i| >= C | |a_i|。加法胜出!C + |a_i|vsC / a_i: 因为C >= 0,C + |a_i|的结果会很大。而除法C / a_i通常会使数的绝对值变小(除非|a_i|是 1)。即使a_i = -1,得到-C,也比不上C + |-1| = C + 1。所以,加法再次胜出!C + |a_i|vsC & |a_i|: 按位与&的结果不会超过参与运算的任何一个数。所以C & |a_i| <= C。而C + |a_i| >= C。加法又赢了,喵~C + |a_i|vsC ^ |a_i|: 同样,对于非负整数x和y,有x + y >= x ^ y。这是因为x + y = (x ^ y) + 2 * (x & y)。既然x & y >= 0,那么加法的结果肯定不会比异或小。
结论
通过上面的分析,我们发现了一个惊人的事实:只要当前的累计结果 res_{i-1} 是非负的,那么在第 步,能让结果最大化的操作总是 加上 |a_i|!
而我们已经证明了,第一步的最大结果 res_1 = |a_1| 是非负的。那么,我们就可以在第二步选择加上 |a_2|,得到 res_2 = |a_1| + |a_2|,这个结果也非负。以此类推,每一步我们都可以选择加上当前数字的绝对值,并且得到的中间结果永远是非负的。
所以,最终的最大结果就是:
a_0 只是一个值为0的起点,对最终的和没有贡献。
这个思路是不是一下子就变得非常清晰简单了呢?喵~
代码实现
现在,我们可以把这个超级简单的策略写成代码啦!我们只需要一个循环,把从 a_1 到 a_n 所有数的绝对值加起来就好。
要注意哦, 最大是 , 最大是 ,它们的乘积可能会达到 ,会超出普通 int 的范围,所以我们要用 long long 来存储总和,防止溢出。
#include <iostream>
#include <cmath> // 用于 abs() 函数
// 为了方便,可以直接使用 std 命名空间
using namespace std;
int main() {
// 使用 stdio sync with false 和 cin.tie(nullptr) 可以加速输入输出,对大数据量很有帮助喵~
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
// a_0 是 0,我们可以直接读进来然后忽略它
long long temp_a0;
cin >> temp_a0;
// 用 long long 来存储总和,防止溢出
long long total_sum = 0;
// 我们需要进行 n 次运算,所以要读入 a_1 到 a_n
for (int i = 0; i < n; ++i) {
long long current_a;
cin >> current_a;
// 根据我们的推导,最优策略是不断累加当前数字的绝对值
total_sum += abs(current_a);
}
// 输出最终的最大结果
cout << total_sum << endl;
return 0;
}复杂度分析
时间复杂度: 我们只需要遍历一次输入的 个数字,所以时间复杂度和数字的数量成正比,也就是 ,非常高效!
空间复杂度: 我们只需要一个变量
total_sum来存储累加的和,以及一个临时变量current_a来读取输入。这些占用的空间是固定的,不随 的增大而增大,所以是常数空间复杂度,即 。
知识点总结
这道题虽然看起来很复杂,但核心思想却非常优美,它教会了我们:
- 化繁为简: 不要被表面复杂的规则(6种运算)吓到。尝试分析小规模的例子,寻找规律。
- 贪心算法与证明: 贪心策略(每步都取最优)并不总是能得到全局最优解。在使用它之前,一定要通过归纳法或者交换论证等方式证明其正确性。这道题就是一个贪心策略有效的绝佳例子。
- 数学性质的应用: 深入理解不同运算(尤其是位运算和算术运算)之间的关系,是解开谜题的关键。
x + y >= x | y这个性质是本题证明的核心。 - 注意数据范围: 在编程竞赛中,要时刻对数据范围保持警惕,就像猫咪对周围环境保持警惕一样!看到大的输入值,就要想到是否需要使用
long long来避免溢出。
希望这篇题解能帮到你,祝你刷题愉快,变得越来越强,喵~!