1. 程式人生 > >51Nod1053 最大M子段和V2 二分+DP

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

而在 理解 題目 然而 註意 發現 names mes 直接

傳送門

直接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;
}

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