输入样例1:
3
3
5 -2 3
4
0 0 0 0
3
1 2 3
输出样例1:
3
0
3
输入样例2:
3
4
-1 -2 -3 7
4
2 3 4 -8
5
-1 -1 6 -1 -1
输出样例2:
5
7
4
分析:这是一道思维跳跃性比较大的题目,比较难想到用前缀和来进行处理
假如我们要使得a[i]减少,根据题意可得,a[i-1]+=a[i],a[i]-=2a[i],a[i+1]+=a[i]对应到前缀和上就是
s[i-1]->s[i],s[i]->s[i-1],s[i+1]->s[i+1],相当于交换s[i]和s[i-1]的位置,那么也就是说我们每次对第i个数进行操作就相当于交换原始前缀和数组中的第i个数和第i-1个数,但是我们不能对第1个数和第n个数进行操作,也就是说我们在交换过程中不能涉及到s[0]和s[n],交换后前缀和数组相邻两个数的差即为答案数组,下面来看下怎样对前缀和数组进行排列,首先容易知道的是我们可以把s[1]~s[n-1]以任意的排列方式进行摆放(冒泡排序可以把任意的序列通过相邻两个数之间的交换而变成有序序列,则有序序列依旧可以通过相邻两个数之间的交换而变成任意序列)
首先s[0]和s[n]的位置是固定的,下面讨论以s[0]<s[n]的条件来进行讨论:
既然s[0]<s[n],则容易知道的是s[1]~s[n-1]中大于s[0]且小于s[n]的数一定排列在相邻的位置且单调递增(图中s0’到sn‘中的数),这个画个图很容易想到,
现在的关键就是我们对于那些小于s0以及大于sn的数怎样排列,这里我们先将那些数进行排列,然后间隔进行选取,最后再回来,举个例子,假如小于等于s0的数的从小到大排序后的序列为偶数,例如s5,s4,s3,s2,s1,s0,那我们的排列就是s0,s2,s4,s5,s3,s1,如果数目要是为奇数,例如s4,s3,s2,s1,s0,那我们的排列就是s0,s2,s4,s3,s1,同理,大于sn的数也是交叉进行选数,可以保证这样选取的数的相邻两个数的差的绝对值的最大值最小,下面给出证明:对于s5,s4,s3,s2,s1,s0,我们的排列就是s0,s2,s4,s5,s3,s1,假如s2与s4之间的距离为最大,那么我们要想减少此距离,必须要使s2走到s3再走到s4,也就是s2,s3,s4要连着,那么就有s5和s1连着或者s5和s0连着,这样会使最大距离变大,显然是矛盾的,所以可以反证得到这种排列方法是最优的。
下面是代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
#define int long long
const int N=300009;
int a[N],s[N],ans[N];
bool vis[N];
signed main()
{
int T;
cin>>T;
while(T--)
{
int n;
cin>>n;
s[0]=0;//初始化
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]),s[i]=s[i-1]+a[i];
int s0=s[0],sn=s[n];
if(s0>sn) swap(s0,sn);
sort(s,s+n+1);
for(int i=0;i<=n;i++)//找到s0的位置
{
if(s0==s[i])
{
s0=i;
break;
}
}
for(int i=0;i<=n;i++)//找到sn的位置
{
if(sn==s[i])
{
sn=i;
break;
}
}
memset(vis,false,sizeof vis);
int l=0,r=n;
for(int i=s0;i>=0;i-=2)
ans[l++]=s[i],vis[i]=true;
for(int i=sn;i<=n;i+=2)
ans[r--]=s[i],vis[i]=true;
for(int i=0;i<=n;i++)
if(!vis[i])
ans[l++]=s[i];
int anss=0;
for(int i=1;i<=n;i++)
anss=max(anss,abs(ans[i]-ans[i-1]));
printf("%lld\n",anss);
}
return 0;
}