1. 程式人生 > >BZOJ 2288 【POJ Challenge】生日禮物(貪心+優先隊列)

BZOJ 2288 【POJ Challenge】生日禮物(貪心+優先隊列)

ace urn ons target challenge pri 最大 font return

【題目鏈接】 http://www.lydsy.com/JudgeOnline/problem.php?id=2288

【題目大意】

  給出一列數,求最多取m段連續的數字,使得總和最大

【題解】

  首先我們對數據進行合並處理,連續的一段正數或者連續的一段負數處理成一個數字,
  之後我們發現,如果正數的個數小於等於m,那麽直接輸出正數的總和即可,
  如果大於m,我們有些正數不選,或者選擇一些負數把左右兩端的正數並起來。
  這個負數的選擇過程相當於減去這個數的絕對值,
  正數選擇拿出去的過程也相當於減去這個數的絕對值,
  在選擇一個負數合並的過程中,兩邊的正數必須是沒有被操作過的,


  同樣,選擇一個正數刪去的過程中,兩邊的負數肯定也必須是沒有操作過的,
  那麽問題就轉化為,給你一些數,請你選擇其中k個不相鄰的數,使得其和最小,
  等同於BZOJ 1150 [CTSC2007]數據備份Backup

【代碼】

#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int,int> P;
const int N=100010;
const int INF=0x3f3f3f3f; 
int n,m,a[N],b[N],l[N],r[N];
int main(){
    while(~scanf("%d%d",&n,&m)){
        for(int i=1;i<=n;i++)scanf("%d",&a[i]);
        while(a[n]<=0)n--;int st=1;
        while(a[st]<=0)st++;
        int cnt=0,ans=0;
        for(;st<=n;st++){if(!((a[st]>0)^(a[st-1]>0)))b[cnt]+=a[st];else b[++cnt]=a[st];}
        for(int i=1;i<=cnt;i++)if(b[i]>0){ans+=b[i];m--;}else b[i]=-b[i];
        if(m>=0){printf("%d\n",ans);continue;}
        priority_queue<P,vector<P>,greater<P> >Q;
        for(int i=1;i<=cnt;i++)l[i]=i-1,r[i]=i+1,Q.push(P(b[i],i));
        r[cnt]=0;
        for(int i=1;i<=-m;i++){
            while(b[Q.top().second]!=Q.top().first)Q.pop();
            int x=Q.top().second;Q.pop();
            ans-=b[x];
            if(!l[x]){b[r[x]]=INF;l[r[x]]=0;}
            else if(!r[x]){b[l[x]]=INF;r[l[x]]=0;}
            else{
                b[x]=b[l[x]]+b[r[x]]-b[x];
                b[l[x]]=b[r[x]]=INF;
                r[l[x]=l[l[x]]]=l[r[x]=r[r[x]]]=x;
                Q.push(P(b[x],x));
            }
        }printf("%d\n",ans);
    }return 0;
}

BZOJ 2288 【POJ Challenge】生日禮物(貪心+優先隊列)