P15292 [MCO 2023] Two Pointers (hard version) 题解
思路
一道巧妙的 DP 优化。首先先考虑如何暴力 DP。假设现在第 \(i\) 个任务刚被做完,那一定有一个人在 \(t_i\)。所以考虑设 \(dp_{i, j}\) 代表做完第 \(i\) 个任务,其中一个人在 \(t_i\),另一个人在 \(j\) 时,两个人路程和的最小值。那我们会发现,对于做第 \(i\) 个任务,有两种选择:
- 上一个任务结束后,一个人在 \(t_{i - 1}\),就让这个人去 \(t_i\) 完成任务,这样的距离是定值,为 \(|t_i - t_{i - 1}|\)。转移方程即为 \(dp_{i, j} = dp_{i - 1, j} + |t_i - t_{i - 1}|\)。
- 让这个在 \(t_{i - 1}\) 的人留在 \(t_{i - 1}\),并让另一个人去 \(t_i\) 完成任务。这样就需要枚举上一个人的位置 \(j\),并转移:\(dp_{i, t_{i - 1}} = \min \limits_j \{ dp_{i - 1, j} + |j - t_i| \}\)。
第一种选择可以直接拿一个变量维护 \(\sum |t_i - t_{i - 1}|\),在代入其他数据时用一个类似于“偏差”的东西解决。关键在于第二种选择。
考虑将这个带绝对值的转移方程拆开来,就可以得到:
\[dp_{i, t_{i - 1}} =
\begin{cases}
(dp_{i - 1, j} + j) - t_i ,& j \ge t_i\\
(dp_{i - 1, j} - j) + t_i,& \text{otherwise.}
\end{cases}
\]
这启发我们用线段树维护 \(dp_j + j\) 和 \(dp_j - j\) 的区间最小值。因为 \(t_i\) 非常大,所以需要离散化。最后注意第一种选择的偏差即可。
时间复杂度 \(\mathcal{O}(n \log n)\),可以通过此题。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 5;
int n, a, b;
int t[N], pos[N];
vector<int> v;
struct seg_tree
{
struct node
{
int l, r, minn, minn2;
} tr[4 * N];
void push_up(int p)
{
tr[p].minn = min(tr[p * 2].minn, tr[p * 2 + 1].minn);
tr[p].minn2 = min(tr[p * 2].minn2, tr[p * 2 + 1].minn2);
}
void build(int p, int l, int r)
{
tr[p] = {l, r, (int)1e18, (int)1e18};
if (l == r) return;
int mid = (l + r) >> 1;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
push_up(p);
}
void modify(int p, int l, int r, int k, int x)
{
if (l == r)
{
tr[p].minn = min(tr[p].minn, x - pos[l]);
tr[p].minn2 = min(tr[p].minn2, x + pos[l]);
return;
}
int mid = (l + r) >> 1;
if (k <= mid) modify(p * 2, l, mid, k, x);
else modify(p * 2 + 1, mid + 1, r, k, x);
push_up(p);
}
int query(int p, int l, int r, bool f)
{
if (tr[p].l >= l && tr[p].r <= r) return (f ? tr[p].minn2 : tr[p].minn);
int mid = (tr[p].l + tr[p].r) >> 1, res = 1e18;
if (l <= mid) res = min(res, query(p * 2, l, r, f));
if (r > mid) res = min(res, query(p * 2 + 1, l, r, f));
return res;
}
} ST;
int calc(int x)
{
return lower_bound(v.begin(), v.end(), x) - v.begin() + 1;
}
signed main()
{
scanf("%lld%lld%lld", &n, &a, &b);
v.push_back(a), v.push_back(b);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &t[i]);
v.push_back(t[i]);
}
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());
for (int i = 0; i < v.size(); i++)
pos[i + 1] = v[i];
int m = v.size(), sum = 0;
ST.build(1, 1, m);
ST.modify(1, 1, m, calc(a), abs(t[1] - b));
ST.modify(1, 1, m, calc(b), abs(t[1] - a));
for (int i = 2; i <= n; i++)
{
int id = calc(t[i]);
int res = min(ST.query(1, 1, id, 0) + t[i], ST.query(1, id + 1, m, 1) - t[i]) + sum;
sum += abs(t[i] - t[i - 1]);
ST.modify(1, 1, m, calc(t[i - 1]), res - sum);
}
int ans = 1e18;
for (int i = 1; i <= m; i++)
ans = min(ans, ST.query(1, i, i, 0) + pos[i]);
printf("%lld\n", ans + sum);
return 0;
}

浙公网安备 33010602011771号