[CEOI2017]Building Bridges
阿新 • • 發佈:2022-03-26
題意簡述
-
有 \(n\) 根柱子,第 \(i\) 根柱子高度為 \(h_i\);拆除一個柱子需要花費 \(w_i\);在柱子 \(i\)、\(j\) 間連線的花費為 \((h_i-h_j)^2\)。你需要保留一些柱子,其中第 \(1\) 和 \(n\) 號柱子必須保留,拆除其餘柱子,並連線相鄰的保留的柱子。
-
你需要確定一個保留方案,使得總花費最小。
-
\(n \le 10^5\),\(h_i, |w_i| \le 10^6\)。
演算法
花費為平方式,這很斜率優化。
設 \(f_i\) 表示考慮前 \(i\) 個柱子並強制保留第 \(i\) 個柱子的最小花費,記 \(s_i=\sum\limits_{j=1}^iw_j\)
用斜優的套路拆拆式子,記 \(X_i=h_i\),\(Y_i=h_i^2+f_i-s_i\),且 \(j \le k\):
\[2h_i(X_k-X_j) \ge {Y_k-Y_j} \]你開心地寫了個單調佇列二分,然後開心的爆零了。
然後你發現這題不僅斜率不單調,連自變數取值也是不單調的。也就是說僅僅單調佇列是沒法維護的。
那怎麼辦呢?
不妨強行讓自變數單調。考慮分治,此時右區間對左邊是沒有貢獻的,所以先計算左區間的 DP 值,然後考慮其對右區間的影響,最後再遞迴右區間。回溯時再按照 \(X_i\)
具體來說,我們在遞迴前先在該區間內按照 \(X_i\) 排序,這樣就保證了自變數和斜率單調不降,故左區間對右區間的轉移直接單調佇列即可。
當遞迴到單個結點 \(l\) 時,它的 DP 值已經計算完了,可以算出 \(Y_l\)。
你發現這就是 CDQ 分治,容易發現像這樣在分治過程中轉移是不會遺漏的。
時間複雜度 \(O(n\log n)\)。
程式碼實現
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int MAXN = 100010; int n; ll h[MAXN], s[MAXN], f[MAXN]; inline ll pw2(ll x) {return x*x;} inline ll X(int i) {return h[i];} inline ll Y(int i) {return pw2(h[i])+f[i]-s[i];} struct node{ ll x, y; int ind; node(ll X=0,ll Y=0,int I=0):x(X),y(Y),ind(I){} bool operator<(const node&o)const{return x ^ o.x ? x < o.x : y < o.y;} }a[MAXN], tmp[MAXN]; int q[MAXN], he, ta; void cdq(int l, int r) { if(l == r) { a[l].y = Y(l); return ; } int mid = (l+r)>>1; int x = l, y = mid+1; for(int i=l;i<=r;i++) if(a[i].ind <= mid) tmp[x++] = a[i]; else tmp[y++] = a[i]; for(int i=l;i<=r;i++) a[i] = tmp[i]; cdq(l, mid); he = 1; ta = 0; for(int i=l;i<=mid;i++) { if(i > l && a[i].x == a[i-1].x) continue; while(he < ta && (a[i].y-a[q[ta]].y)*(a[q[ta]].x-a[q[ta-1]].x) <= (a[q[ta]].y-a[q[ta-1]].y)*(a[i].x-a[q[ta]].x)) --ta; q[++ta] = i; } for(int i=mid+1;i<=r;i++) { while(he < ta && a[q[he+1]].y-a[q[he]].y <= 2*a[i].x*(a[q[he+1]].x-a[q[he]].x)) ++he; int u = a[i].ind, v = a[q[he]].ind; f[u] = min(f[u], f[v] + s[u-1] - s[v] + pw2(h[u] - h[v])); } cdq(mid+1, r); x = l; y = mid+1; int now = l; while(x <= mid && y <= r) { if(a[x].x < a[y].x || (a[x].x == a[y].x && a[x].y < a[y].y)) tmp[now++] = a[x++]; else tmp[now++] = a[y++]; } while(x <= mid) tmp[now++] = a[x++]; while(y <= r) tmp[now++] = a[y++]; for(int i=l;i<=r;i++) a[i] = tmp[i]; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%lld",h+i), a[a[i].ind = i].x = h[i]; for(int i=1;i<=n;i++) scanf("%lld",s+i), s[i] += s[i-1]; for(int i=2;i<=n;i++) f[i] = 0x3f3f3f3f3f3f3f3f; sort(a+1, a+n+1); cdq(1, n); printf("%lld\n",f[n]); return 0; }