CF1038D Slime

Description

一个序列中不断取出两个数 (A,B) 进行 (A - B),再把 (A - B) 丢进去,最大化最后剩下的一个数。

好像是比赛原题,题还没加先来写一下这篇。

Link

Solution

首先左右两边不是左右两只史莱姆,而是左右两边所有的。

我们显然要得出一个最大数,一个最小数,使得答案最大。

因为有正数和负数,要分类讨论 :

先假设一个由小到大的序列 (A)

  • 全为非负数

我们可以先排序,用最小的那一只减去除最大的以外的所有数,最后再用最大的减去这个数的,即 :

[egin{aligned} Ans &= A_n - (A_1 - sum_{i = 2} ^ {n - 1}A_i)\&=sum_{i = 2} ^ n - A_1 end{aligned} ]

因为现在整个序列全为正数,我们要确保最大,因为大的数字对答案贡献大,不能枚举相邻两项来是答案减小,构造一个负数,来用它减去除最大的以外的全部正数,就可以得到最小的解,再用最大的数减去即可。

  • 全为负数

和全为正数一样,我们可以反过来想,我们能不能用两个最大的数构造一个正数,再用这个数减去其他数,答案是肯定的,即

[egin{aligned} Ans &= (A_n - A_{n - 1}) - sum_{i = 1} ^ {n - 2}A_i \ &= A_n - sum_{i = 1} ^ {n - 1}A_i end{aligned} ]

证明同上。

  • 数有正有负

我们发现这种情况下,我们完全可以用所有负数减去除最大值以外的所有正数,再用最大数一一减去这些负数。

假设到从 (1 cdots j) 为负数,从 (j + 1 cdots n) 为正数。

[egin{aligned} Ans &= A_{n} - (sum_{i = 1} ^ jA_i - sum_{i = j + 1} ^ {n - 1}A_i)\&= sum_{i = j + 1} ^ nA_i - sum_{i = 1} ^ j A_i\&=sum_{i = 1} ^ n left|A_i ight | end{aligned} ]

实在看不懂可以自己手膜一下。

还有就是要判断一个数的情况,这种情况下只能输出原数。

Code

#include <cstdio>
#include <cmath>
#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int Maxk = 500010;
int a[Maxk],Ans,n,sum,tot;
inline int read()
{
	int s = 0, f = 0;char ch = getchar();
	while (!isdigit(ch)) f |= ch == '-', ch = getchar();
	while (isdigit(ch)) s = s * 10 + (ch ^ 48), ch = getchar();
	return f ? -s : s;
}
int Abs(int x) {return x > 0 ? x : -x;}
signed main()
{
  n = read();
  for(int i = 1;i <= n;i ++) {
    a[i] = read();
    sum += a[i];
    tot += Abs(a[i]);
  }
  if(n == 1) {
    cout << a[1] << endl;
    return 0;
  }
  sort(a + 1,a + n + 1);
  if(a[1] >= 0) Ans = sum - a[1] - a[1];
  else if(a[n] < 0) Ans = a[n] + a[n] - sum;
  else Ans = tot;
  cout << Ans << endl;
  return 0;
}
原文地址:https://www.cnblogs.com/Ti-despair/p/14698087.html