题意:
给定长度为n的序列,一次操作你可以将一个元素移动到另外一个位置,
你最多进行这个操作一次,
问最后是否可以将这个序列分为前后两个非空子段,
满足两端的元素和相等
数据范围:n<=1e5,a(i)<=1e9
解法:
设序列元素和为tot,那么两端的和一定为tot/2
如果tot是奇数肯定无解,
枚举前缀和sum,设x=tot/2-sum,
如果x>0,那么看能否从后面的数中找到一个x放到前面
如果x<0,那么看能否从前面的数中找到一个x放到后面
如果能找到说明有解
容易想到二分查找加速,但是得让数有序
左右两端各开一个multiset就行了
-----分割线-----
这题还有一种解法,利用前缀和数组的有序性:
枚举a[i],在前缀和数组上二分找第一个tot/2-a[i]的位置pl,
如果pl在i的左边,说明有解.
二分找第一个tot/2+a[i]的位置pr,
如果pr在i的右边,说明有解.
原理和前面的解法类似
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
int sum[maxm];
int a[maxm];
int n;
signed main(){
cin>>n;
if(n==1){
cout<<"NO"<<endl;
return 0;
}
int tot=0;
for(int i=1;i<=n;i++)cin>>a[i],tot+=a[i];
if(tot%2){
cout<<"NO"<<endl;
return 0;
}
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
//
multiset<int>s1,s2;
for(int i=1;i<=n;i++)s2.insert(a[i]);
auto it=s2.lower_bound(tot/2);
if(it!=s2.end()&&*it==tot/2){
cout<<"YES"<<endl;
return 0;
}
//
for(int i=1;i<=n;i++){
s2.erase(s2.find(a[i]));
s1.insert(a[i]);
int x=tot/2-sum[i];
if(x==0){
if(!s1.empty()&&!s2.empty()){
cout<<"YES"<<endl;
return 0;
}
}else if(x>0){
if(s2.size()>1){//保证集合2非空
auto it=s2.lower_bound(x);
if(it!=s2.end()&&*it==x){
cout<<"YES"<<endl;
return 0;
}
}
}else if(x<0){
x=-x;
if(s1.size()>1){
auto it=s1.lower_bound(x);
if(it!=s1.end()&&*it==x){
cout<<"YES"<<endl;
return 0;
}
}
}
}
cout<<"NO"<<endl;
return 0;
}