题解 P7522 ⎌ Nurture ⎌
题意简述
你有一个长度为 \(n\) 的序列 \(v\),你每次可以 取出 两个数 \(a,b\),并把 \(a-b\) 添加到序列中。重复操作直到序列中只剩下一个数,你需要求出这个数的最大值。
\(1 \leq n \leq 3 \times 10^5 ,0 \leq |v_i| \leq 10^9\) 。
Solution
首先有一个结论,它可能可以帮助您理解下面的式子:因为题目只有减法(有的时候减去一个负数会变成加上一个正数),所以答案 \(\leq \sum_{i=1}^n |v_i|\) 。
然后下文讨论的 \(n\) 都 \(\geq 2\) ,因为 \(n=1\) 的时候不需要操作(当 \(n=1\) 的时候答案是 \(v_1\) ),如果讨论 \(n=1\) 会很麻烦。
首先将 \(v\) 从小到大排序,然后分三种情况讨论:
- \(\forall i \in[1,n]v_i\geq 0\)
那么最后一步一定是形如 \(a-b\) 这样的式子。
这个式子最大, \(a\) 应该尽量大, \(b\) 应该尽量小,那么 \(a\) 取 \(v_n\) 就可以了。
至于 \(b\) 的话,它是一堆数相减,因为所有的数都是正数,又要让这个数最小,所以把最小值当做被减数,剩下的全部当做减数(也就是 \(v_1-v_2-v_3\cdots\) ),所以答案就是
直接计算即可,复杂度 \(\mathcal{O}(n)\) 。
- \(\forall i \in[1,n] v_i \leq 0\)
因为 \(a-(-b)=a+b\) ,而这一部分的所有数字都是负数,所以可以让 \(n-1\) 个数字的相反数(一定是正数)累加到答案里面去,剩下一个数按照原来的值累加到答案里去。
那按照原来的值累加到答案里的那个数字肯定要是最大的(绝对值最小的),剩下的数字一定是变了号。
答案为:
- \(v\) 中既有正数,又有负数(也可能有 \(0\) )
首先可以保证的是 \(v_n\) 是正数, \(v_1\) 是负数。
那么可以用 \(v_1\) 减去 \(v_1\sim v_{n-1}\) 中的所有正数都以它的相反数的值累加到 \(v_1\) 中,剩下的 \(v_1\sim v_{n-1}\) 中的数字就全部都是负数了。
再使用 \(v_n\) 逐个把这些数字减去,就能得到最大值了。
这么做之所以是正确的,是因为最后得到的是正数,并且在任何一步操作中,留下来的值的绝对值一定不比原来小、
还有一种方法也可以伪证明:考虑答案是什么,因为正数都以相反数的值累加到 \(v_1\) 里面去了,而 \(v_1\) 又以它的相反数累加到 \(v_n\) 中了,所以这些正数相当于间接地以它本身值累加到 \(v_n\) 中去了。
对于负数,它直接以它的相反数的值累加到了 \(v_n\) 中。
综上, \(v_i\) 对答案的贡献就是 \(|v_i|\) ,最终答案为:
它与 Solution 开头说的答案最大值是一样的,所以这一定是最优的方法。
几个注意的地方:
- 记得特判 \(n=1\) 的情况。
- 记得开
long long
。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <queue>
typedef long long LL;
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 3e5 + 5;
int n ,a[N];
signed main() {
n = read();
for (int i = 1; i <= n; i++) a[i] = read();
if (n == 1) {
printf("%d\n" ,a[1]);
return 0;
}
sort(a + 1 ,a + n + 1);
if (a[n] <= 0) {
LL ans = a[n];
for (int i = 1; i <= n - 1; i++) ans -= a[i];
printf("%lld\n" ,ans);
}
else if (a[1] >= 0) {
LL ans = -a[1];
for (int i = 2; i <= n; i++) ans += a[i];
printf("%lld\n" ,ans);
}
else {
LL ans = 0;
for (int i = 1; i <= n; i++) ans += a[i] < 0 ? -a[i] : a[i];
printf("%lld\n" ,ans);
}
return 0;
}
好像第一次比赛自己想出这种题(逃