首页 > 试题广场 >

显生之宙

[编程题]显生之宙
  • 热度指数:912 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
众数归于本初
生命就此苏生
白垩色的王子给了你一个由 n 个数组成的数列,并告诉你,你需要执行 n-1 次如下操作:
选定一个数列中的数 x,再选择数列中的至少一个其他数,然后让这些数都加上 x,再将 x 移除出这个数列。
n-1 次操作过后,数列中最后只会剩下一个数。白垩色的王子希望你告诉他,最后剩下的那个数最小是多少。题目保证最后输出的答案的绝对值 \le 4\times 10^{18}

输入描述:
第一行输入一个整数 T(1 \le T \le 10^5),表示数据组数。
对于每一组数据,第一行输入一个整数 n(1 \le n\le\sum n \le 5 \times 10^5),表示初始数列中有 n 个数。
接下来一行,输入 n 个数 a_1,a_2\dots a_n(-10^9 \le a_i \le 10^9),表示初始数列。


输出描述:
对于每一组数据,输出一行一个整数,表示最后剩下的那个数的最小值。
示例1

输入

2
2
1 2
4
-1 8 -2 0

输出

3
-2

说明

对于第一组数据:一种方案是令 2 加上 1,结果为 3
对于第二组数据:一种方案是先令其他三个数各加上 -2,数列变为 -3,6,-2,再令其他两个数各加上 -3,数列变为 3,-5,最后令 -5 加上 3,结果为 -2
示例2

输入

1
6
-1 -2 -3 100 200 300

输出

549

说明

一种方案是先令其他五个数各加上 -3,数列变为 -4, -5,97,197,297,再令其他四个数各加上 -5,数列变为 -9,92,192,292,再令其他三个数各加上 -9,数列变为 83,183,283,然后三个数依次加给下一个数,得到答案。
首先,我们要设计贪心策略:

 我们的目标是最终结果最小,显然负数尽量多加几次,正数尽量只加一次。
我们发现,越先选中的数字,可加的次数越多,那我们就按升序先排个序
排序后,我们从第1个数起,只要是负数就把它加满,一个一个往后推
直到只剩下正数时,就要改变策略了,每个正数只加一次,就是最终结果了。

策略设计好了,但代码能这样写吗?
不能!为什么?如果这样写,复杂度就是O(n2),太慢了!
怎么办?看视频的同学应该记得前几天老师讲过:拆贡献
什么是拆贡献?我们要计算每个数加了多少次,就是它的贡献。

我们开始拆贡
假设排序过的原始数列:a1 a2 a3 a4 a5(我们假设全是负数)
第1轮过后:a2+a1 a3+a1 a4+a1 a5+a1
每个数加了a1,计作delta1=a1
第2轮过后:a3+a1+a2+a1 a4+a1+a2+a1 a5+a1+a2+a1
每个数在加了a1的基础上又加了a1+a2,计作delta2=a1*2+a2
第3轮不再列举,因为所有数都加过前2轮的delta,这回加的第3个数也含有前两轮的delta
可以推导出:delta3=(a1*2+a2)*2+a3
这里就能看出规律:deltai=deltai-1*2+ai
还记得前面的策略吗?只剩正数时停止(当然还有一种情况,就是只剩一个数了)
我们要搞清楚,这里说的正数是现在的数,而不是原数列的数,也要加delta的
由于数列排过序,所以只要遇到正数,后面就全都是正数了。
接下来就好办了,把剩下的所有数(每个数都加过delta)加起来就完了

照着上面逻辑写代码就很简单了(就这几行,不写注释也能看懂):
Arrays.sort(array);
long delta = 0;
int index = 0;
long ans = 0;
while (index < n - 1 && array[index] + delta < 0) {
    delta = delta * 2 + array[index++];
}
while (index < n) {
    ans += array[index++] + delta;
}
System.out.println(ans);


发表于 2026-03-13 04:31:16 回复(1)