1. 程式人生 > 其它 >[CEOI2017]Building Bridges

[CEOI2017]Building Bridges

題目連結

題意簡述

  • \(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\)

\[f_i = \min\{f_j+s_{i-1}-s_j+(h_i-h_j)^2\} \]

用斜優的套路拆拆式子,記 \(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\)

為第一關鍵字,\(Y_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;
}