【Luogu P4360】[CEOI2004]锯木厂选址

题目大意:

一座山,山脚下有个锯木厂,山上有 \(n\) 棵树,把一棵树砍掉的费用是它的重量乘离锯木厂的距离,如果再在山上设置两个锯木厂,求把所有树砍掉的最小费用。

正文:

考虑直接用动态规划。设 \(f_i\) 表示第二个锯木厂设置在 \(i\) 处的最小费用,\(F_{i,j}\) 表示从 \((i+1)\)\(j\) 这一段的费用(即在 \(i\)\(j\) 处建锯木厂时,从 \((i+1)\)\(j\) 的费用),那么动态转移方程就是:

\[f_i=\operatorname{min}_{j=1}^{i-1}\{F_{0,j}+F_{j,i}+F_{i,n+1}\} \]

接下来考虑 \(F_{i,j}\)

\[F_{i,j}=\sum_{k=i+1}^{j-1}\left(\sum_{l=k+1}^{j-1}d_l\right)w_k \]

其中 \(\sum_{l=k+1}^{j}d_l\),完全可以用前缀和预处理出,这里用 \(D_i\) 表示(这里 \(D_i=\sum_{j=1}^{i-1}d_j\)):

\[\begin{aligned}F_{i,j} & = \sum_{k=i+1}^{j-1}\left(D_j-D_k\right)w_k \\ & = \sum_{k=i+1}^{j-1}D_j\cdot w_k-D_k\cdot w_k \\ & = \left(\sum_{k=i+1}^{j-1}w_k\right)D_j-\left(\sum_{k=i+1}^{j-1}w_k\cdot D_k\right)\end{aligned} \]

其中 \(\sum_{k=i+1}^{j-1}w_k\)\(\sum_{k=i+1}^{j-1}w_k\cdot D_k\) 可以用前缀和预处理,这里用 \(W_i,S_i\) 表示(这里 \(S_i=\sum_{j=1}^{i}w_j\cdot D_j,W_i=\sum_{j=1}^{i}w_j\)):

\[\begin{aligned}F_{i,j} & =\left(W_{j-1}-W_i\right)D_j-\left(S_{j-1}-S_i\right)\\ & = W_{j-1}\cdot D_j-W_i\cdot D_j-S_{j-1}+S_i\end{aligned} \]

则:

\[f_i=\min_{j=1}^{i-1}\{\left(W_{j-1}\cdot D_j-W_0\cdot D_j-S_{j-1}+S_0\right)+\left(W_{i-1}\cdot D_i-W_j\cdot D_i-S_{i-1}+S_j\right)+\left(W_{n}\cdot D_{n+1}-W_i\cdot D_{n+1}-S_{n}+S_i\right)\} \]

假设有 \(j,k(j,k<i)\)\(j\) 的决策比 \(k\) 更优的情况:

\[\left(W_{j-1}\cdot D_j-W_0\cdot D_j-S_{j-1}+S_0\right)+\left(W_{i-1}\cdot D_i-W_j\cdot D_i-S_{i-1}+S_j\right)+\left(W_{n}\cdot D_{n+1}-W_i\cdot D_{n+1}-S_{n}+S_i\right)\leq\left(W_{k-1}\cdot D_k-W_0\cdot D_k-S_{k-1}+S_0\right)+\left(W_{i-1}\cdot D_i-W_k\cdot D_i-S_{i-1}+S_k\right)+\left(W_{n}\cdot D_{n+1}-W_i\cdot D_{n+1}-S_{n}+S_i\right) \]

最后可求得斜率方程:

\[\frac{(W_{j-1}\times D_{j}-S_{j-1}+S_{j})-(W_{i-1}\times D_i-S_{i-1}+S_i)}{W_j-W_i} \leq D_i \]

代码:


double slope(int x, int y)
{
	return (double)((w[y - 1] * sumd[y] - a[y - 1] + a[y]) - (w[x - 1] * sumd[x] - a[x - 1] + a[x]) - 0.0) / (w[y] - w[x] + 0.0);
}

ll ans = 1ll << 60;
int main()
{
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	scanf ("%lld", &n);
	for (int i = 1; i <= n; ++i)
		scanf ("%lld%lld", &w[i], &d[i]);
	for (int i = 1; i <= n + 1; ++i)
	{
		sumd[i] = sumd[i - 1] + d[i - 1];
		a[i] = a[i - 1] + w[i] * sumd[i];
		w[i] += w[i - 1];
	}
	for (int i = 1; i <= n; i++)
	{
		while(head < tail && slope(que[head], que[head + 1]) <= (double)sumd[i])
			head++;
		f[i] = (w[que[head] - 1] * sumd[que[head]] - a[que[head] - 1]) + (w[i - 1] * sumd[i] - w[que[head]] * sumd[i] - a[i - 1] + a[que[head]]) + (w[n] * sumd[n + 1] - w[i] * sumd[n + 1] - a[n] + a[i]);
		ans = min (ans, f[i]);
		while(head < tail && slope(que[tail], i) <= slope(que[tail - 1], que[tail]))
			tail--;
		que[++tail] = i;
	}
	printf ("%lld", ans);
    return 0;
}

posted @ 2020-03-08 22:22  Jayun  阅读(122)  评论(0编辑  收藏  举报