Blog of RuSun

\begin {array}{c} \mathfrak {One Problem Is Difficult} \\\\ \mathfrak {Because You Don't Know} \\\\ \mathfrak {Why It Is Diffucult} \end {array}

P2120 [ZJOI2007]仓库建设

P2120 [ZJOI2007]仓库建设

显然有一个性质,任何时候在 $i$ 点结束,则 $i$ 一定要建立一个仓库。定义状态 $f _ i$ 表示在 $i$ 点建立仓库 $1\ldotsi$ 完成的最小代价。那么则有
$$
\begin {aligned}
f _ i & = c _ i + \min _ {j < i} \{ f _ j + \sum _ {k = j + 1} ^ i p _ k (d _ i - d _ k) \} \\
& = c _ i + \min _ {j < i} \{ f _ j + d _ i \sum _ {k = j + 1} ^ i p _ k - \sum _ {k = j + 1} ^ i p _ k d _ k) \}
\end {aligned}
$$
对 $p$ 和 $pd$ 做前缀和,
$$
\begin {aligned}
& f _ i = c _ i + \min _ {j < i} \{ f _ j + d _ i (sp _ i - sp _ j) - (sdp _ i - sdp _j) \} \\
\Longrightarrow & f _ i - c _ i - d _ i sp _ i + sdp _ i= \min _ {j < i} \{ f _ j + sdp _j - d _ i sp _ j \}
\end {aligned}
$$
维护下凸包斜率优化即可。

Hack :存在 $p _ i = 0$ 。这意味着存在 $sp _ i = sp _ j$ 以及 $sdp _ i = sdp _ j$ ,此时点重合,斜率不存在,重复的点不添加即可;在取答案时,不一定在 $f _ n$ ,最后一段 $p _ i = 0$ 的区间和第一个 $p _ i \neq 0$ 的点均可以取答案。

查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int hd = 1, tl, q[N];
int n;
LL d[N], p[N], c[N], sp[N], sdp[N], f[N];
double slp(int a, int b)
{
return (double)(f[a] + sdp[a] - f[b] - sdp[b]) / (double)(sp[a] - sp[b]);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%lld%lld%lld", &d[i], &p[i], &c[i]);
sp[i] = sp[i - 1] + p[i];
sdp[i] = sdp[i - 1] + d[i] * p[i];
}
q[++tl] = 0;
for (int i = 1; i <= n; i++)
{
while (hd < tl && slp(q[hd], q[hd + 1]) < d[i])
hd++;
f[i] = c[i] + f[q[hd]] + d[i] * (sp[i] - sp[q[hd]]) - sdp[i] + sdp[q[hd]];
while (hd < tl && slp(q[tl - 1], q[tl]) > slp(q[tl], i))
tl--;
if (p[i])
q[++tl] = i;
}
LL res = f[n];
for (int i = n - 1; i >= 0 && !p[i + 1]; i--)
res = min(res, f[i]);
printf("%lld", res);
return 0;
}