ABC334F

· · 题解

思路:

由于是按顺序来发礼物,那么显然可以考虑 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 不太好维护,所以考虑拆成前缀和:

然后把与 $i$ 有关的提出来: $f_{i} = \min \{f_j + dist(0,j + 1)-sum_j\} + dist(i, 0) + sum_i$。 于是 $\min$ 中的直接用线段树做即可(其实也可以单调队列滑动窗口)。 ## Code: ```cpp #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; } ```