本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2023-12-24 18:28:04
思路:
由于是按顺序来发礼物,那么显然可以考虑 DP。
那么设 $f_{i}$ 表示发了前 $i$ 个,并且当前在 $0$ 的最小的距离花费,$dist(i,j)$ 表示第 $i$ 个点和第 $j$ 个点的距离,第 $0$ 个点表示起点,$cost(i, j)$,表示 $i$ 按顺序走到 $j$ 点的距离和。
那么 $f_{i} = \min \{f_j + dist(0,j + 1) + cost(j + 1, i) + dist(i, 0)\}$。
然后因为 $cost$ 既有 $i$ 也有 $j$ 不太好维护,所以考虑拆成前缀和:
$f_{i} = \min \{f_j + dist(0,j + 1) + sum_i-sum_j + dist(i, 0)\}$。
然后把与 $i$ 有关的提出来:
$f_{i} = \min \{f_j + dist(0,j + 1)-sum_j\} + dist(i, 0) + sum_i$。
于是 $\min$ 中的直接用线段树做即可(其实也可以单调队列滑动窗口)。
Code:
#include <bits\/stdc++.h>
using namespace std;
int n, k;
pair<double, double> a[200010];
double f[200010], s[200010];
double tr[800010];
void modify(int u, int l, int r, int x, double d) {
if (l == r) {
tr[u] = d;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(u << 1, l, mid, x, d);
else modify(u << 1 | 1, mid + 1, r, x, d);
tr[u] = min(tr[u << 1], tr[u << 1 | 1]);
}
double query(int u, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) return tr[u];
int mid = (l + r) >> 1;
double res = 1e18;
if (ql <= mid) res = min(res, query(u << 1, l, mid, ql, qr));
if (qr > mid) res = min(res, query(u << 1 | 1, mid + 1, r, ql, qr));
return res;
}
double dist(pair<double, double> a, pair<double, double> b) {
return sqrt((a.first - b.first) * (a.first - b.first) + (a.second - b.second) * (a.second - b.second));
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 0; i <= n; ++i) scanf("%lf%lf", &a[i].first, &a[i].second);
for (int i = 0; i <= n; ++i) f[i] = 1e18;
for (int i = 1; i <= n; ++i) s[i] = s[i - 1] + dist(a[i - 1], a[i]);
f[0] = 0;
modify(1, 0, n, 0, f[0] + dist(a[0], a[1]) - s[1]);
for (int i = 1; i <= n; ++i) {
f[i] = query(1, 0, n, max(0, i - k), i - 1) + s[i] + dist(a[i], a[0]);
if (i < n) modify(1, 0, n, i, f[i] - s[i + 1] + dist(a[i + 1], a[0]));
}
printf("%.15lf\n", f[n]);
return 0;
}

鲁ICP备2025150228号