复制代码

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)

posted @ 2021-09-29 16:12  Elgina  阅读(92)  评论(0)    收藏  举报