[蓝桥杯 2019 省 B] 灵能传输
题目背景
在游戏《星际争霸 II》中,高阶圣堂武士作为星灵的重要 AOE 单位,在游戏的中后期发挥着重要的作用,其技能“灵能风暴”可以消耗大量的灵能对一片区域内的敌军造成毁灭性的伤害。经常用于对抗人类的生化部队和虫族的刺蛇飞龙等低血量单位
题目描述
你控制着 n n n 名高阶圣堂武士,方便起见标为 1 , 2 , ⋯ , n 1,2, \cdots,n 1,2,⋯,n。每名高阶圣堂武士需要一定的灵能来战斗,每个人有一个灵能值 a i a_i ai 表示其拥有的灵能的多少( a i a_i ai 非负表示这名高阶圣堂武士比在最佳状态下多余了 a i a_i ai 点灵能, a i a_i ai 为负则表示这名高阶圣堂武士还需要 − a i -a_i −ai 点灵能才能到达最佳战斗状态)。现在系统赋予了你的高阶圣堂武士一个能力,传递灵能,每次你可以选择一个 i ∈ [ 2 , n − 1 ] i \in[2,n-1] i∈[2,n−1],若 a i ≥ 0 a_i \ge 0 ai≥0 则其两旁的高阶圣堂武士,也就是 i − 1 i-1 i−1 、 i + 1 i+1 i+1 这两名高阶圣堂武士会从 i i i 这名高阶圣堂武士这里各抽取 a i a_i ai 点灵能;若 a i < 0 a_i<0 ai<0 则其两旁的高阶圣堂武士,也就是 i − 1 , i + 1 i-1,i+1 i−1,i+1 这两名高阶圣堂武士会给 i i i 这名高阶圣堂武士 − a i -a_i −ai 点灵能。形式化来讲就是 ( a i − 1 , a i , a i + 1 ) ← ( a i − 1 + a i , − a i , a i + 1 + a i ) (a_{i-1},a_i,a_{i+1})\leftarrow (a_{i-1}+a_i,-a_i,a_{i+1}+a_i) (ai−1,ai,ai+1)←(ai−1+ai,−ai,ai+1+ai)。
灵能是非常高效的作战工具,同时也非常危险且不稳定,一位高阶圣堂武士拥有的灵能过多或者过少都不好,定义一组高阶圣堂武士的不稳定度为 max i = 1 n { ∣ a i ∣ } \max\limits_{i=1}^n\{|a_i|\} i=1maxn{∣ai∣},请你通过不限次数的传递灵能操作使得你控制的这一组高阶圣堂武士的不稳定度最小。
输入格式
本题包含多组询问。输入的第一行包含一个正整数 T T T 表示询问组数。
接下来依次输入每一组询问。
每组询问的第一行包含一个正整数 n n n,表示高阶圣堂武士的数量。
接下来一行包含 n n n 个数 a 1 , a 2 , ⋯ , a n a_1,a_2, \cdots,a_n a1,a2,⋯,an。
输出格式
输出 T T T 行。每行一个整数依次表示每组询问的答案。
样例 #1
样例输入 #1
3 3
5 -2 3
4
0 0 0 0
3
1 2 3
样例输出 #1
3
0
3
样例 #2
样例输入 #2
3 4
-1 -2 -3 7
4
2 3 4 -8
5
-1 -1 6 -1 -1
样例输出 #2
5
7
4
样例 #3
样例输入 #3
见文件trans3.in。
样例输出 #3
见文件trans3.ans。
提示
【样例说明】
对于第一组询问:
对 2 2 2 号高阶圣堂武士进行传输操作后 a 1 = 3 a_1=3 a1=3, a 2 = 2 a_2=2 a2=2, a 3 = 1 a_3=1 a3=1。答案为 3 3 3。
对于第二组询问:
这一组高阶圣堂武士拥有的灵能都正好可以让他们达到最佳战斗状态。
【数据规模与约定】
对于所有评测用例, T ≤ 3 T \le 3 T≤3, 3 ≤ n ≤ 3 × 1 0 5 3 \le n \le 3\times10^5 3≤n≤3×105, ∣ a i ∣ ≤ 1 0 9 |a_i| \le 10^9 ∣ai∣≤109。
评测时将使用 25 25 25 个评测用例测试你的程序,每个评测用例的限制如下:
蓝桥杯 2019 年省赛 B 组 J 题。
思路
我们通过题目要求,发现如下情况:(不管你
a
i
>
0
a_{i}>0
ai>0还是
<
0
<0
<0,都满足如下式子)
本道题的难点就是:我们得想到前缀和(为什么我们能想的到前缀和?因为你没有发现上图式子有没有点类似等差数列我乱说的),然后就可以发现:
看看蓝色的si和绿色的si,看看,好像只有
s
i
−
1
和
s
i
的
位
置
交
换
了
s_{i-1}和s_{i}的位置交换了
si−1和si的位置交换了,然后我们就根据这个性质,我们可以知道,对于任意可交换子序列(不包括头和尾),那么我们只需他们前缀和的两个进行交换即可。
然后我们带入样例来看:发现:
我们只需要让二者的max(s[i]-s[i-1])最小,这时候我们又知道,(此时必须满足一个条件就是s0和sn可以交换顺序)当s[i]的曲线为直线的话,此时最小(为什么呢?因为每个相邻的s[i]都满足差为一个定值,若为曲线的话,那么可能会出现比较大的s[i]-s[i-1]的值),但是,本道题又要求s0和sn不能交换顺序,因此我们可以画出以下图:
为什么要画成这个样子呢?因为(我的理解,可能是错的)一个序列一定存在一个最大值和一个最小值,如果不是有序序列的话,即最大值和最小值不在两边的时候,就会出现曲线(也就是我们可以假假换一下,这样就可以看成直线了)
接着:
注意点:(就是为什么图是右上图)很重要,本道题的关键
这样我们就可以把试着把上述图映射到y轴上,如图所示:(为什么能映射,因为我们要保证max(si-si-1)最小,如果不是像右上图,那么重叠部分比较多的话,那么跳跃的距离就大,因此不能让重叠部分比较大,图中y轴的值就是si)
为什么这么跳:
总的来说就是如果我们一个一个的跳,那么当从s0跳到min再从min跳到s0的右边就会有一个很大大飞跃,这样就会导致max(si-si-1)变大,然后我们再考虑隔一个跳,诶,刚刚好。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N = 3e5+10;
int w[N],T,s[N],a[N];
bool st[N];
int n;
signed main(){
cin>>T;
while(T--){
cin>>n;
memset(st,0,sizeof st);
for(int i=1;i<=n;i++){
cin>>w[i];
s[i]=s[i-1]+w[i];
}
//s[0]与s[n]不能动
//我们也假设s[0]<s[n];
int s0=s[0],sn=s[n];
if(s0>sn)swap(s0,sn);
sort(s,s+1+n);
for(int i=0;i<=n;i++){
if(s[i]==s0){
s0=i;
break;
}
}
//s0和sn只是记录下标的
for(int i=n;i>=0;i--){
if(s[i]==sn){
sn=i;
break;
}
}
//极大值与极小值已经过了
//二段跳最优
int l=0,r=n;
//往左边跳
for(int i=s0;i>=0;i-=2){
a[l++]=s[i];
st[i]=true;
}
for(int i=sn;i<=n;i+=2){
a[r--]=s[i];
st[i]=true;
}
for(int i=0;i<=n;i++){
if(!st[i]){//中间过程
a[l++]=s[i];
}
}
//这样已经对上述排序好了(按如图所示排序好了)
int res=0;
for(int i=1;i<=n;i++){
res=max(res,abs(a[i]-a[i-1]));
}
cout<<res<<endl;
}
return 0;
}