1. 程式人生 > >[P1484] 種樹

[P1484] 種樹

+= 一個 貪心 Go ble getch 選項 while 決策

Link:https://www.luogu.org/problemnew/show/P1484

Brief Introduction:在一個長為N上的序列取至多K個數,要保證所取的數兩兩不相鄰,求Max(所取的數的和)。

N<=5e5,K<=N/2

Algorithm:

1、首先O(NK)的dp是能立刻想到的 dp[n][k]=max(dp[n-1][k],dp[n-2][k-1]+value[n])

可以使用滾動數組優化

但明顯不足以解決N<=5e5的問題

2、可以從最簡單的問題開始考慮,如果K=1,那麽取最大值。設該點的位置為P。

當問題擴大時,棘手的問題在於不易維護所取數兩兩不相鄰這一條件

為了每次選取最大值時可以忽略這個條件,我們選擇P後將P-1,P,P+1合並,看為同一個點,這樣保證接下來可以任意選點

為了提供“反悔”這個選項,新點的權值設為value(P-1)+value(P+1)-value(P),如選擇新點則表示“反悔”不選P,而改選P-1和P+1.

維護數列中插入、刪除、求最大值,可以想到使用堆/Priority_queue維護

Code:

#include <bits/stdc++.h>

using namespace std;

inline 
int read() { char ch;int f=0,num; while(!isdigit(ch=getchar())) f|=(ch==-); num=ch-0; while(isdigit(ch=getchar())) num=num*10+ch-0; return f?-num:num; } #define F first #define S second const int MAXN=5e5+10; typedef pair<int,int> P; typedef long long ll; priority_queue<P> Q;
bool vis[MAXN]; ll l[MAXN],r[MAXN],dat[MAXN]; int main() { int n=read(),k=read(); for(int i=1;i<=n;i++) { dat[i]=read(); l[i]=i-1;r[i]=i+1; //維護每個點的兩邊的點的坐標 Q.push(P(dat[i],i)); } ll res=0;l[0]=1;r[n+1]=n; //對邊界設置 for(int i=1;i<=k;i++) { while(vis[Q.top().S]) Q.pop(); //如已被合並,則不處理 P t=Q.top();Q.pop(); if(t.F<0) break; res+=t.F;vis[l[t.S]]=vis[r[t.S]]=true; dat[t.S]=t.F=dat[l[t.S]]+dat[r[t.S]]-dat[t.S]; Q.push(t); r[t.S]=r[r[t.S]];l[t.S]=l[l[t.S]]; //不真正合並,僅維護左右點坐標 l[r[t.S]]=t.S;r[l[t.S]]=t.S; } cout << res; return 0; }

Review:

1、當遇到常見問題被加以特殊條件時,註意化歸,通過轉化將問題變為易解問題

  能否忽略特殊條件解題

2、在決策類問題中,我們可以利用貪心+“反悔”選項的方式解決問題

將權值變為 其他解-當前貪心解

3、有時在維護合並操作時,不一定要真正合並。

可將信息集中在其中一點上,只維護每個點相鄰點坐標即可。

同時,要對“廢點”打上標記,之後對其不再處理

[P1484] 種樹