灵能传输
友情链接:灵能传输
题目:
输入样例:
3 3 5 -2 3 4 0 0 0 0 3 1 2 3
输出样例:
3 0 3
思路:
题目大意:给出一个数组,每次选择数组中的一个数(要求不能是第一个数与最后一个数),如果这个数是一个正数,就将这个数减去自身两次,并且将相邻的两个数分别加上这个数一次,如果这个数是负数,就将这个数减去自身两次,并且将相邻的数加上这个负数两次,(本质上第二种情况与第一种情况一样,因为减去负数相当于加上这个负数的绝对值),使用公式表述为: a i − 1 + = a i a i + 1 + = a i a i − = 2 a i a_{i - 1} += a_i ~~~~~~~~ a_{i + 1} += a_i~~~~~~~~a_i -= 2a_i ai−1+=ai ai+1+=ai ai−=2ai,并且记住选择的i
不能是第一个数或最后一个数。
具体思路:
通过尝试可知,公式与每一个数的前缀和有极大的关系,举例:对于数组5 -2 3 4
而言,其前缀和为5 3 6 10
,如果选择的是-2
,那么原数组的值会变为:3 2 1 4
,变化后的数组前缀和为:3 5 6 10
,相当于将原数组的前缀和中的第一个位置与第二个位置进行了交换,再看还是对于数组5 -2 3 4
而言,如果选择的是3
,那么原数组的值会变为5 1 -3 7
,对应的前缀和为5 6 3 10
,相当于对原数组的前缀和数组的第二个位置与第三个位置进行了交换。下图为更直观的理解:
由此我们可以得出一个规律:如果对某一个位置i
进行操作,相当于对前缀和数组中i
与i - 1
位置的值进行交换。其中:1 < i < n
。
这样我们就可以得出一个简单的解决方案了,题目要求的是使原数组中数值的绝对值的最大值最小化,一个简单的思路是对前缀和数组进行排序,因为这样可以保证相邻两个前缀和数值的差值最小,这样就保证了原数组中的数值的最大值最小化。
但是题目还有一个条件,就是不能对第一个位置和最后一个位置进行操作,如果直接对前缀和数组(前缀和数组表示为: S S S)进行排序(包含了 S 0 S_0 S0和 S n S_n Sn),那么就违反了题目的要求。我们这样思考:如果对 a 1 a_1 a1(原数组用 a a a表示)进行操作,那么对应的前缀和变化是交换 S 0 S_0 S0和 S 1 S_1 S1(其中 S 0 = 0 S_0 = 0 S0=0 ),如果对 a n a_n an进行操作,那么对应的前缀和数组的变化是交换 S n S_n Sn和 S n − 1 S_{n - 1} Sn−1,为了不使 S 0 S_0 S0和 S n S_n Sn移动,我们这两个数进行移动,我们需要让起点仍然是因为 S 0 S_0 S0和 S n S_n Sn,但为了使相邻两个值之间的差值最小,我们需要使用一些策略:如从 S 0 S_0 S0的位置进行步长为2
向前进行取值正序填充到一个新的数组中去并且进行标记,在 S n S_n Sn的位置也进行步长为2
向后进行取值,逆序(即:从新数组的最后一个位置开始进行填充)填充到一个新的数组中去并且进行标记,最后从头开始遍历排序后的前缀和数组,将还未标记的值按顺序一次填充到新的数组的空余位置。
还有一个问题: S 0 S_0 S0如果大于 S n S_n Sn该如何解决?
对于这种情况:我们只需要在查找 S 0 S_0 S0和 S n S_n Sn的位置的时候将 S 0 S_0 S0和 S n S_n Sn的位置进行交换即可,这样就变为了 S n S_n Sn向前进行步长为2
的取值, S 0 S_0 S0向后进行步长为2
的移动。下图为一个直观的理解:
记得要使用long long
数据类型,因为int类型的数据最大在 1 0 9 10^9 109左右,而题目要求的 a i a_i ai的值小于等于 1 0 9 10^9 109,其前缀和数组的值可能会超过int
的存储容量。
代码:
#include<bits/stdc++.h>
using namespace std;typedef long long ll;
void solve(){int n; cin>>n;vector<ll> A(n + 1, 0);vector<ll> S(n + 1, 0);for(int i = 1;i <= n;i ++){cin>>A[i];S[i] += S[i - 1] + A[i];}// 记录S中的第一个位置与最后一个位置ll front = S[0];ll end = S[n];if(front > end) swap(front, end);// 对前缀和数组进行顺序排序sort(S.begin(), S.end()); // 排序的时候包含了第0个位置的数 // 找到原来S数组的第一个位置和最后一个位置的数在排序后的数组中的下标for(int i = 0;i <= n;i ++){if(S[i] == front){front = i;break;}}for(int i = n;i >= 0;i --){ // 这里遍历也可以从0开始到n结束if(S[i] == end){end = i;break;}}vector<ll> ans(n + 1, 0);// 设定标记数组vector<ll> cnt(n + 1, 0);ll frontidx = 0;ll endidx = n; // 从idxf向前进行找for(int i = front;i >= 0;i -= 2){ans[frontidx++] = S[i];cnt[i] = 1;}// 从idxe向后进行找 for(int i = end;i <=n ;i += 2){ans[endidx --] = S[i];cnt[i] = 1;}// 将剩下的数进行填充 for(int i = 0;i <= n;i ++){if(!cnt[i]){ans[frontidx++] = S[i];}}// 从ans数组中找到相邻两个数之间的最小的值ll tans = 0;for(int i = 1;i <= n;i ++){tans = max(tans, abs(ans[i] - ans[i - 1]));}cout<<tans<<endl;return ;
} signed main(){ios::sync_with_stdio(0);cin.tie(0);int t = 1; cin>>t; // t组询问 while(t--){solve();}return 0;
}