题目描述
题目背景
在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 AOE 单位,在 游戏的中后期发挥着重要的作用,其技能"灵能风暴"可以消耗大量的灵能对 一片区域内的敌军造成毁灭性的伤害。经常用于对抗人类的生化部队和虫族的 刺蛇飞龙等低血量单位。
问题描述
你控制着 n 名高阶圣堂武士,方便起见标为 1,2,··· ,n。每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 ai 表示其拥有的灵能的多少,ai非负表示这名高阶圣堂武士比在最佳状态下多余了 ai 点灵能,ai 为负则表示这名高阶圣堂武士还需要 −ai 点灵能才能到达最佳战斗状态)。
现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i ∈ [2,n−1],若 ai ≥ 0 则其两旁的高阶圣堂武士,也就是 i−1、i + 1 这两名高阶圣堂武士会从 i 这名高阶圣堂武士这里各抽取 ai 点灵能;若 ai < 0 则其两旁的高阶圣堂武士,也就是 i−1,i+1 这两名高阶圣堂武士会给 i 这名高阶圣堂武士 −ai 点灵能。形式化来讲就是 a{i−1} += ai,a{i+1} += ai,ai −= 2ai。
灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为:
请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。
输入描述
本题包含多组询问。
输入的第一行包含一个正整数 T 表示询问组数。 接下来依次输入每一组询问。
每组询问的第一行包含一个正整数 n,表示高阶圣堂武士的数量。
接下来一行包含 n 个数 a1,a2,··· ,an。
其中,T≤3,3≤n≤300000,∣ai∣≤10^9.
输出描述
输出 T 行。每行一个整数依次表示每组询问的答案。
输入输出样例
示例
输入
3
3
5 -2 3
4
0 0 0 0
3
1 2 3
输出
3
0
3
运行限制
- 最大运行时间:1s
- 最大运行内存: 256M
问题分析:
有a[i-1], a[i], a[i+1], 对a[i]进行一次传递灵能后,变为a[i-1]+a[i], -a[i], a[i+1]+a[i]。
前缀和变化如下:(规定S[0] = 0,即a[1] = S[1] - S[0] = S[1] - 0)
S[i-1]变为:S[i-2] + a[i-1] + a[i] = S[i]
S[i]变为:S[i-2] + a[i-1] + a[i] - a[i] = S[i-1]
S[i+1]变为:S[i] + a[i+1] + a[i] = S[i-1] + a[i] + a[i+1] = S[i+1],即不变
所以a[i]进行一次传递灵能后,达到的效果是将S[i-1]和S[i]交换。
例如:
a[]:2 3 4 -8 S[]:2 5 9 1
对a[3] = 4进行一次传递灵能后得到:2 7 -4 -4, S[]变为:2 9 5 1
相反地,交换前缀和数列中的相邻两项S[i-1]和S[i],达到的效果是对a[i]进行一次传递灵能。
例如:
S[]:2 5 9 1 交换S[1]和S[2]得到 5 2 9 1
a[]: 2 3 4 -8 变为 5 -3 7 -8
|ai| = | S[i]-S[i-1] |
因 i ∈ [2, n-1]才可进行灵能传输,故S[0] 和 S[n]的位置固定不动,中间的 S[1]~S[n-1] 可以任意调整顺序。
因此本题等价于通过交换除 S[0] 和 S[n] 以外的任何S[i-1]和S[i],使得S[i]-S[i-1]的最大值最小化,即前缀和序列中相邻两项的最大差值尽可能小,要得到这样的最优解应使得S序列尽量保持单调。
最优的序列一定为:S0->最小值(即S[0])->...->最大值(即S[n])->Sn
但是S0到达最小值S[0]的过程中,如果一步到达,那么差值一定会很大,所以必须跳着走。
如果分几步从S0到达最小值S[0],再单调上升一直到最大值S[n],再一步步下降直到S[n],会使答案更优,要做到每一步相邻两项差值都尽量小,可以发现每隔一个向前跳是最好的。
具体策略:
在S0每隔一个向前跳,Sn每隔一个向后跳的过程中,拿到的数标记为已拿,最后将没有用过的数串起来就是中间单调的数,这样构成的序列就是最优的前缀和序列S。
最后求S相邻两项差值的最大值。
代码示例:
#include <bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<LL> S(n + 1); //前缀和,还要存S0,各元素初值为0
for (int i = 0; i < n; i++) {
int x;
cin >> x;
S[i + 1] = S[i] + x;
}
LL S0 = S[0], Sn = S[n];
if (S0 > Sn)
swap(S0, Sn);
sort(S.begin(), S.end());
for (int i = 0; i <= n; i++) { //找S0的位置(如果有多个值为S0,则任意取一个即可)
if (S[i] == S0) {
S0 = i;
break;
}
}
for (int i = 0; i <= n; i++) { //找Sn位置
if (S[i] == Sn) {
Sn = i;
break;
}
}
vector<LL> a(n + 1); //存处理后的前缀和序列
vector<bool> v(n + 1); //排序后每个数取出与否的标志
int l = 0, r = n;
for (int i = S0; i >= 0; i -= 2) { //让S0跳着走到最小值S[0]
a[l++] = S[i];
v[i] = true;
}
for (int i = Sn; i <= n; i += 2) { //让Sn反跳着走到最大值S[n]
a[r--] = S[i];
v[i] = true;
}
for (int i = 0; i <= n; i++) { //剩下的数按顺序取
if (!v[i])
a[l++] = S[i];
}
LL ans = 0;
for (int i = 1; i <= n; i++) {
ans = max(ans, abs(a[i] - a[i - 1]));
}
cout << ans << endl;
}
return 0;
}