1. 程式人生 > >種樹 (堆模擬網絡流)

種樹 (堆模擬網絡流)

ace iostream () 輸入輸出 cond algorithm break etc air

種樹

題目描述

cyrcyr今天在種樹,他在一條直線上挖了n個坑。這n個坑都可以種樹,但為了保證每一棵樹都有充足的養料,cyrcyr不會在相鄰的兩個坑中種樹。而且由於cyrcyr的樹種不夠,他至多會種k棵樹。假設cyrcyr有某種神能力,能預知自己在某個坑種樹的獲利會是多少(可能為負),請你幫助他計算出他的最大獲利。

輸入輸出格式

輸入格式:

第一行,兩個正整數n,k。

第二行,n個正整數,第i個數表示在直線上從左往右數第i個坑種樹的獲利。

輸出格式:

輸出1個數,表示cyrcyr種樹的最大獲利。

輸入輸出樣例

輸入樣例#1: 復制

6 3
100 1 -1 100 1 -1

輸出樣例#1: 復制

200

說明

對於20%的數據,n<=20。

對於50%的數據,n<=6000。

對於100%的數據,n<=500000,k<=n/2,在一個地方種樹獲利的絕對值在1000000以內。

題解

一道非常好的堆模擬網絡流題。
首先我們來想想\(O(n^2)\)
很顯然的兩重循環dp
第一重枚舉第i個位置,第二重枚舉選了j顆樹。
對於O(n)的模擬。
我們首先明確選了 i 就不能選 i-1 和 i+1。
所以所有的情況都是由選 i 和選 i-1 ,i+1 產生的。
選 i 時, i 的貢獻應當是大於 i-1 和 i+1 的。
但是當我們發現選 i-1 和 i+1 要更好時呢?
網絡流有一個操作就是反悔。
我們是不是也可以反悔一下呢?

因為先選了 i ,那麽下一次選擇的時候,如果反悔就是選兩邊
就是選了周圍兩個點,不反悔就是選其他點
那麽把周圍兩個點維護為一個點就可以了。
\(vi[i]=vi[l[i]]+vi[r[i]]-vi[i]\)
然後維護一下 \(l\)\(r\) 數組。

代碼

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
priority_queue<pair<int,int> >q;
int vis[1000001],ch[1000001],n,m;
long long ans;
struct node{
    int vi,id,l,r;
}t[1000001];
int read(){
    int x=0,w=1;char ch=getchar();
    while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*w;
}

int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++){
        ch[i]=t[i].vi=read();t[i].id=i;
        t[i].l=i-1;t[i].r=i+1;
        q.push(make_pair(ch[i],i));
    }
    t[0].r=1;t[n+1].l=n;
    while(m&&!q.empty()){
        while(vis[q.top().second])q.pop();
        int x=q.top().second;q.pop();
        if(t[x].vi<0)break;
        ans+=t[x].vi;
        t[x].vi=t[t[x].l].vi+t[t[x].r].vi-t[x].vi;
        vis[t[x].l]=vis[t[x].r]=1;
        t[x].l=t[t[x].l].l;t[t[x].l].r=x;
        t[x].r=t[t[x].r].r;t[t[x].r].l=x;
        q.push(make_pair(t[x].vi,x));
        m--;
    }
    printf("%lld",ans);
    return 0;
}

種樹 (堆模擬網絡流)