【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;
}