题解 P7522 ⎌ Nurture ⎌

题意简述

Link

你有一个长度为 \(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\) 从小到大排序,然后分三种情况讨论:

  1. \(\forall i \in[1,n]v_i\geq 0\)

那么最后一步一定是形如 \(a-b\) 这样的式子。

这个式子最大, \(a\) 应该尽量大\(b\) 应该尽量小,那么 \(a\)\(v_n\) 就可以了。

至于 \(b\) 的话,它是一堆数相减,因为所有的数都是正数,又要让这个数最小,所以把最小值当做被减数,剩下的全部当做减数(也就是 \(v_1-v_2-v_3\cdots\) ),所以答案就是

\[\begin{aligned} v_n-(v_1-v_2-v_3-\ldots) \\ =v_n-v_1+v_2+v_3+\ldots \\ =-v_1+\sum_{i=2}^n v_i \end{aligned} \]

直接计算即可,复杂度 \(\mathcal{O}(n)\)

  1. \(\forall i \in[1,n] v_i \leq 0\)

因为 \(a-(-b)=a+b\) ,而这一部分的所有数字都是负数,所以可以让 \(n-1\) 个数字的相反数(一定是正数)累加到答案里面去,剩下一个数按照原来的值累加到答案里去。

那按照原来的值累加到答案里的那个数字肯定要是最大的(绝对值最小的),剩下的数字一定是变了号。

答案为:

\[v_n-\sum_{i=1}^{n-1} v_i \]

  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|\) ,最终答案为:

\[\sum_{i=1}^n |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;
}

好像第一次比赛自己想出这种题(逃

posted @ 2021-04-25 00:53  recollector  阅读(58)  评论(0编辑  收藏  举报