思路分析
分析样例 1:
3
1 3 2
原数组被分成 1
和 3 2
两部分,将 2
移到左边即可。
我们设左边部分的和为 \(s1\),右边为 \(s2\),可以发现对于任何分割方式,只有满足 \(s1 \pm x=s2 \mp x\) 才可以继续讨论答案是否成立。
推论 1:由于 \(x \in a\)(\(a\) 为题目所给数列),因此 \(| \ s1-s2 \ |\equiv 0 \ (\ \bmod{ \ 2} \ )\)(换句话说,\(s1\) 和 \(s2\) 的差值必然是偶数)。
推论 2:只有当 \(\dfrac{| \ s1-s2 \ |}{2} \in a\) 时,才能成立。
我们枚举 \(p \in \{0 \ , \ n+1\}\),得出区间 \(a_{[\ 1 \ ,\ p \ ]}\)、\(a_{[\ p+1\ ,\ n\ ]}\) 之和,根据以上推论编写代码,可以用桶查询 \(\dfrac{| \ s1-s2 \ |}{2}\) 是否在原数列。
但真的如此吗?
考虑下面的样例:
5
1 2 2 5 4
这本来应该是 NO
的,但如果你只按照上述所说很有可能会输出 YES
。
按照上面所述,你的程序在将原数列分割成 1 2 2
和 5 4
时,\(\dfrac{| \ s1-s2 \ |}{2}\) 为 2,而 2 在桶中是可以被找到的,也就是说,你的程序认为从右边可以往左边移动一个 2,这样就成立了。
再仔细看看样例,2 分明在左边,右边是没有 2 的。
没错,你还要判断当应该从某一边移动数字时,你要判断该数字是否在这一边。
以上就是本道题的思路。如若存在常识性错误,请踢本蒟蒻。
代码实现
#define by_wanguan
#include<iostream>
#include<map>
#include<vector>
#define int long long
#define pb emplace_back
#define end cout<<"YES",exit(0)
using namespace std;
const int N=1e5+7;
int n,a[N],sum,p,s1,s2;
map<int,vector<int>> vis;
inline int abs(int &a,int &b){
if(a>b) return a-b;
else return b-a;
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],sum+=a[i],vis[a[i]].pb(i);
//记录a中a[i]出现的位置,方便后续判断abs(s2-s1)/2是否在区间内
s2=sum,s1=0;
for(p=0;p<=n+1;p++){//枚举左右区间分界线
s1+=a[p],s2-=a[p];
if(abs(s2-s1)%2!=0) continue;
if(s1==s2) end;
auto &pe=vis[abs(s2-s1)/2];
if(s1<s2)
{if(!pe.empty()&&pe.back()>p) end;}
//由于上面的位置记录时单调的,只需要取最靠后面的位置就能判断右区间内是否有abs(s2-s1)/2
//下面同理
if(s1>s2)
{if(!pe.empty()&&pe.front()<=p) end;}
}
cout<<"NO";
}
千万不要用 unordered_map
,你会后悔的,警钟长鸣。