2021年泉州市信息学奥赛集训活动(国庆集训)CSP-S Day3

石子合并

题目描述

操场上有 \(N\) 堆石子排成一排,每次 \(Kiana\) 可以选择两堆相邻的石子进行合并,并在原地得到一堆新的石子,直到所有的石子都被合并为一堆。

\(Kiana\) 的石子自然是与众不同的,具体而言,初始时每堆石子都有一个奇妙值,这个值可能是任意整数。在合并两堆石子后,得到的新石子堆的奇妙值为两堆石子的奇妙值之差。由于两个正整数作差不满足交换律,因此 \(Kiana\) 可以自由决定是由哪堆石子的奇妙值减去另一堆石子的奇妙值来得到结果。

现在 \(Kiana\) 想知道,将操场上所有石子合并为一堆后,能得到的最大的奇妙值是多少。由于 \(Kiana\) 自己不会算,所以希望你能够帮助她。

输入输出格式

输入格式

第一行包含一个正整数 \(T\),表示输入数据的组数。

接下来依次输入各组数据,每组数据第一行包含一个正整数 \(N\),表示操场上石子堆数。

每组数据第二行包含 \(N\) 个整数,其中第 \(i\) 个数 \(S_i\) 表示初始时第 \(i\) 堆石子的奇妙值。

输出格式

输出共一行,包含一个整数,表示将操场上所有石子合并为一堆后能得到的最大的奇妙值。

输入输出样例

输入样例#1:

1
3
-1 0 2 

输出样例#1:

3

输入样例#2:

5
5
191 229 973 939 971 
5
415 837 652 17 105 
5
960 188 703 782 325 
5
243 653 21 281 471 
5
930 16 77 130 866 

输出样例#2:

2921
1992
2582
1627
1987

样例解释

操场上有 \(3\) 堆石子,其奇妙值依次为 \(-1,0\)\(2\)

\(Kiana\) 首先将第 \(2\) 堆和第 \(3\) 堆石子合并,并用第 \(3\) 堆石子的奇妙值减去第 \(2\) 堆石子的奇妙值得到新石子堆奇妙值为 \(2\)。然后将新石子堆和第 \(1\) 堆石子合并,并用前者奇妙值减去后者奇妙值得到最终的奇妙值为 \(3\),可以证明这是奇妙值最大的方案。

数据范围

对于 \(20\%\) 的数据,保证 \(1\leq N\leq 200\)

对于 \(40\%\) 的数据,保证 \(1\leq N\leq 2000\)

对于 \(70\%\) 的数据,保证 \(1\leq N\leq 2\times 10^5\)

对于 \(100\%\) 的数据,保证 \(1\leq N\leq 2\times 10^6,1\leq T\leq 5,-1000\leq S_i\leq 1000\)

分析

  • 情况一:正负数都存在,那我们一定可以调整出最后是正数减负数的情况,也就是答案为所有数的绝对值之和。
    • 比如 \(-1,6,2,-3,2,2,1\),我们可以先将 \(-1\) 减去 \(2, 2, 2, 1\)\(4\) 个数,得到 \(-8\)
    • 那么石子堆就变成了 \(-8, 6, -3\) 那么我们可以用 \(6\) 减去 \(-8\)\(-3\),最后得到 \(17\) 这个最大值
  • 情况二:都是正数,这个时候就没办法让答案变成所有数的绝对值之和了,那么我们应该选择构建出一个负数来让式子变成情况一。
    • 注意到我们构建的负数不同会对最后的答案有影响
    • 因此,我们可以需要构建负数过程中的的损耗最小,也就是求 \(min\{|s_i|+|s_j|-|s_i-s_j|\}\)
    • (其实就是将最小的 \(s_i\) 减去就可以了)
    • 最后减去即可
  • 情况三:都是负数,其实这个情况跟情况二一样,是转化出一个正数,使得损耗最小

代码

Code
#include <cstdio>
#include <climits>
#include <algorithm>

typedef long long ll;

const int maxN = 2e6 + 12;

template<class T>
void read(T &x) {
	x = 0;
	T f = 1;
	char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-')
			f = -1;
		c = getchar();
	}
	while ('0' <= c && c <= '9') {
		x = x * 10 + (c ^ 48);
		c = getchar();
	}
	x *= f;
}

int T, N;
ll a[maxN], s;
bool flag, tag;
/*
flag == 1 代表有正数和负数
tag == 0 代表第一个数是负数
tag == 1 代表第一个数是正数
*/

int main(void) {
	read(T);
	
	while (T --) {
		read(N);
		
		flag = 0;
		s = 0;
		for (int i = 1; i <= N; ++i) {
			read(a[i]);
			s += abs(a[i]);
			if (i == 1) {
				tag = ((a[i] > 0) ? 1 : 0);
			}
			
			if ((tag == 1 && a[i] < 0) || (tag == 0 && a[i] > 0)) {
				flag = 1;
			}
		}
		
		if (flag) {
			printf("%lld\n", s);
		}
		else {
			int mn = INT_MAX;
			if (N == 1) {
				printf("%lld\n", a[1]);
				continue;
			}
			
			for (int i = 1; i <= N; ++i) {
				mn = std::min(mn, abs(a[i]));
			}
			
			printf("%lld\n", s - 2 * mn);
		}
	}
	
	return 0;
}

铺地毯(To be continued)

优美的旋律(To be continued)

基站建设(To be continued)

posted @ 2021-10-04 23:40  Juro  阅读(322)  评论(0编辑  收藏  举报