Codeforces808 D. Array Division(二分)

题意:

给定长度为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;
}

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/107794762
今日推荐