51Nod1053 最大M子段和V2 二分+DP

传送门

直接DP的话最多也只能做到\(O(nm)\),对于\(5\times 10^4\)的数据范围实在无能为力
夹克老爷提供的做法是贪心,思想大概是在调整的同时,合理构造每个选择对应的新状态,使得新状态的一些选择可以代表“反悔”当前决策
(然而我没看懂……要是我看懂了也就不会有这个做法了)
其实还有另一种可能更好理解的做法

我们不妨考虑一种类似王钦石二分的思路
可以为每段额外加上一个相同的损失,在之后求最优解时不再考虑段数的限制
不难发现这个损失越大答案的段数就会越少,损失越小段数就会越多,存在单调性
所以我们可以二分这个损失,最后一定能找到一个损失值使得在此前提下存在一种段数满足要求的解
(并不严谨的证明:显然对于一个确定的损失值,最优解的段数是一个区间,然而在损失值+1时得到的最多段数其实就是当前损失值得到的最少段数-1,换句话说这些区间可以覆盖所有可能的段数)
方便起见,可以直接求最优解前提下最少的段数,这样我们只需要找到最少段数\(\le m\)时最小的损失值即可
注意最后求最优解时得到的段数不一定恰好是\(m\),但这种情况其实无所谓,因为出现这种情况时,一定是考虑损失后多加几段最优解不变,所以不用担心
注意二分上下界取\(10^9\)是不够的,但是鉴于题目的特殊性,取所有正数的和一定够了

#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
int solve(long long);
long long f[maxn],tmp;
int n,m,a[maxn];
int main(){
    scanf("%d%d",&n,&m);
    int cnt=0;
    long long sum=0;
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        cnt+=a[i]>0;
        if(a[i]>0)sum+=a[i];
    }
    if(cnt<=m)printf("%lld",sum);
    else{
        f[0]=-0x3f3f3f3f3f3f3f3fll;
        long long L=-sum,R=sum;
        while(L!=R){
            long long M=(L+R)>>1;
            if(solve(M)<=m)R=M;
            else L=M+1;
        }
        cnt=solve(L);
        printf("%lld",tmp+m*L);
    }
    return 0;
}
int solve(long long M){
    long long ans=1ull<<63,mxf=0;
    int cnt=0,mxcnt=0,anscnt=0;
    for(int i=1;i<=n;i++){
        f[i]=max(f[i-1],mxf-M)+a[i];
        if(f[i]!=f[i-1]+a[i])cnt=mxcnt+1;
        else if(f[i]==mxf-M+a[i])cnt=min(cnt,mxcnt+1);
        if(ans<f[i]){
            ans=f[i];
            anscnt=cnt;
        }
        else if(ans==f[i])anscnt=min(anscnt,cnt);
        if(f[i]>mxf){
            mxf=f[i];
            mxcnt=cnt;
        }
        else if(f[i]==mxf)mxcnt=min(mxcnt,cnt);
    }
    tmp=ans;
    return anscnt;
}

猜你喜欢

转载自www.cnblogs.com/hzoier/p/9362714.html