bzoj 2151 種樹 題解
阿新 • • 發佈:2021-08-07
【分析】
反悔貪心
若沒有相鄰不可選的限制,則用優先佇列維護前 \(m\) 大,然後直接求和即可
現考慮對貪心如何反悔:
假設我們處於開局,什麼都沒選的狀態
當貪心選擇第 \(i\) 個時,由於相鄰不可選,需要刪除 \((i-1)\) 和 \((i+1)\) 的權值,避免產生相鄰的選擇
但實際上有可能 \(a_{i-1}+a_{i+1}>a_i\) 從而反悔之前的貪心,故我們補回一個點,權值為 \(a_{i-1}+a_{i+1}-a_i\)
若後續過程中,我們選擇了該點,則代表我們反悔了選擇 \(a_i\) 的這次貪心;但同時,我們選擇的點數仍然+1
此時,我們刪除點 \((i-2)\)
但與上面相同,實際上可能 \(a_{i-2}+a_i+a_{i+2}>a_{i-1}+a_{i+1}>a_i\) 從而反悔之前的貪心,故我們同樣補回一個點,權值為 \(a_{i-2}+a_i+a_{i+2}-a_{i-1}-a_{i+1}=a_{i-2}+a_{i+2}-(a_{i-1}-a_{i+1}-a_i)\)
而這次過程等價於將上一步新產生的補充點看成同樣的一個點,再進行上一步的補充操作
因此我們不區分補充的點與原有的點,直接將原有的點塞進優先佇列中,並維護一個迴圈連結串列
當我們從優先佇列中取出一個未被打過標記的點時,將它與它前後,共三個點打上標記
之後,我們新建權值為 \(a_{i-1}+a_{i+1}-a_i\) 的點,並在迴圈佇列中頂替原來的三個點即可
執行 \(m\) 次得到答案
【程式碼】
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int, int> pii; typedef double db; #define fi first #define se second const int MAXN=4e5+10; int n, m, a[MAXN], pre[MAXN], suf[MAXN], cntc; bool used[MAXN]; priority_queue<pii> pq; inline int maxc(){ int val, pos; while(pq.size()){ val=pq.top().fi; pos=pq.top().se; pq.pop(); if(!used[pos]) break; } if(used[pos]) return 0; used[pos]=used[ pre[pos] ]=used[ suf[pos] ]=1; a[++cntc]=a[ pre[pos] ]+a[ suf[pos] ]-a[pos]; suf[ pre[pre[pos]] ]=cntc; pre[ suf[suf[pos]] ]=cntc; pre[cntc]=pre[pre[pos]]; suf[cntc]=suf[suf[pos]]; pq.push( pii(a[cntc], cntc) ); return val; } inline void init(){ cin>>n>>m; for(int i=1;i<=n;++i){ cin>>a[i]; pre[i]=i-1; suf[i]=i+1; pq.push( pii(a[i], i) ); } suf[n]=1; pre[1]=n; cntc=n; } int main(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); init(); if(n<m+m){ cout<<"Error!"; } else{ int res=0; for(int i=1;i<=m;++i) res+=maxc(); cout<<res; } cout.flush(); return 0; }