[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;
}
posted @ 2022-03-26 10:17  Aphrosia  阅读(24)  评论(0编辑  收藏  举报