acwing 3816题解
移动元素
题目描述
给定一个长度为 n 的正整数数组 a1,a2,…,an。
你需要选择其中一个元素,将其移动至数组中的任意位置(也可以留在原位置)。
我们的目标是,在移动元素操作完成以后,将数组分为前后两个非空部分,并使前一部分的各元素之和等于后一部分的各元素之和。
请问,该目标能否达成?
数据范围
1 ≤ T ≤ 20,
1 ≤ n ≤ \(10^5\) ,
1 ≤ ai ≤ \(10^9\)。
输入
3
3
1 3 2
5
1 2 3 4 5
5
2 2 3 4 5
输出
YES
NO
YES
分析
1.当前缀和是奇数时,必定不能被平分。
2.当前一部分或后一部分已经能够让这相等时,不必再移动。
只需要一个前缀和一个后缀和,枚举即可。
3.当不得不移动时,那么能平分的位置在中间的那条线上,
那么存在一个值能移动使平分,
这个值必然是(sum[i] - sum[n]/2)
即判断这个值是否存在即可。
4.用哈希表判断有无出现过(unordered_set),注意开long long
5.同样回到第二条,sum[i] - sum[n] = 0 ,就不需要移动了。
代码
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <unordered_set>
#include <cstring>
using namespace std;
typedef long long ll;
const int N=2e6;
int t,n;
int a[N],b[N];
ll sum[N];
bool check(int w[])
{
memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)
sum[i] = sum[i-1] + w[i];
unordered_set<ll> S;
if(sum[n] % 2 == 1) return false;
S.insert(0);
for(int i=1;i<=n;i++)
{
S.insert(w[i]);
if(S.count(sum[i] - sum[n]/2)) return true;
}
return false;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1,j=n;i<=n;i++,j--)
{
scanf("%d",&a[i]);
b[j] = a[i];//逆序数组
}
if(check(a) || check(b)) puts("YES");
else puts("NO");
}
return 0;
}
时间复杂度
O(nlogn)