若想要深刻學習反悔貪心,傳送門。html
Description:node
有 \(n\) 個位置,每一個位置有一個價值。有 \(m\) 個樹苗,將這些樹苗種在這些位置上,相鄰位置不能都種。求能夠獲得的最大值或無解信息。c++
Method:數組
先判斷無解的狀況,咱們顯然能夠發現,若 \(n<\frac{2}{m}\) ,則是不能在合法的條件下種上 \(m\) 棵樹的,故按題意輸出Error!
便可。學習
假若有解的話,咱們能夠很輕鬆的推出貪心策略:在合法的狀況下選擇最大的價值。spa
顯然上面的策略是錯誤的,咱們選擇了最大價值的點,相鄰的兩個點就不能選,而選擇相鄰兩個點獲得的價值可能更大。設計
考慮如何設計反悔策略。code
咱們一樣用差值來達到反悔的目的。假設有 \(A\) ,\(B\) ,\(C\) ,\(D\) 四個相鄰的點(如圖)。htm
\(A\) 點的價值爲 \(a\) ,其餘點同理。若:
\[ a+c>b+d \]blog
則:
\[ a+c-b>d \]
假如咱們先選了 \(B\) 點,咱們就不能選 \(A\) 和 \(C\) 兩點,這顯然是不對的,但咱們能夠新建一個節點 \(P\) , \(P\) 點的價值爲 \(a+c-b\) ,再刪去 \(B\) 點。(如圖,紅色的是刪去的點,橙色的新建的點)
下一次選擇的點是 \(P\) 的話,說明咱們反悔了(即至關於 \(B\) 點沒有選),能夠保證最後的貪心最優解是全局最優解。
如何快速插入 \(P\) 點和找出是否選擇 \(P\) 點呢?咱們能夠使用雙向鏈表和小根堆,使得最終在 \(O(n\log n)\) 的時間複雜度下快速求出全局最優解。
Code:
#include<bits/stdc++.h> #define int long long #define Maxn 2000010 using namespace std; inline void read(int &x) { int f=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=f; } int n,m; int w[Maxn],lft[Maxn],rgh[Maxn]; struct node { int val,id; bool operator <(const node &n) const { return val<n.val; } }; priority_queue<node>qu; int ind,ans=0; int vis[Maxn]; signed main() { read(n),read(m); ind=n; if(n/2<m) { puts("Error!"); return 0; } for(int i=1;i<=n;i++) { read(w[i]); node tmp; tmp.id=i; tmp.val=w[i]; qu.push(tmp); if(i==1) { lft[i]=n; rgh[i]=i+1; }else if(i==n) { lft[i]=i-1; rgh[i]=1; }else { lft[i]=i-1; rgh[i]=i+1; } } for(int i=1;i<=m;i++) { while(vis[qu.top().id]) qu.pop(); int id=qu.top().id; int val=qu.top().val; qu.pop(); ans+=val; ind++; vis[lft[id]]=vis[rgh[id]]=1; lft[rgh[rgh[id]]]=ind;rgh[lft[lft[id]]]=ind; lft[ind]=lft[lft[id]];rgh[ind]=rgh[rgh[id]]; w[ind]=w[lft[id]]+w[rgh[id]]-val; int newid=ind; int newval=w[ind]; node tmp; tmp.id=newid; tmp.val=newval; qu.push(tmp); } printf("%lld\n",ans); return 0; }
Warning:
必定要記錄這個點選沒有選過,假如已經選過了,就從堆中丟出去;
1與 \(n\) 是相鄰的,必定要特判一下;
雙向鏈表必定不要寫掛了;
必定要先將新建的點的價值存入一開始的價值數組,再丟進堆裏;(卡在45卡了很久)
index
是關鍵字,必定不要使用。(我成功CE了一次)