P15292 [MCO 2023] Two Pointers (hard version) 题解

题目传送门

思路

一道巧妙的 DP 优化。首先先考虑如何暴力 DP。假设现在第 \(i\) 个任务刚被做完,那一定有一个人在 \(t_i\)。所以考虑设 \(dp_{i, j}\) 代表做完第 \(i\) 个任务,其中一个人在 \(t_i\),另一个人在 \(j\) 时,两个人路程和的最小值。那我们会发现,对于做第 \(i\) 个任务,有两种选择:

  1. 上一个任务结束后,一个人在 \(t_{i - 1}\),就让这个人去 \(t_i\) 完成任务,这样的距离是定值,为 \(|t_i - t_{i - 1}|\)。转移方程即为 \(dp_{i, j} = dp_{i - 1, j} + |t_i - t_{i - 1}|\)
  2. 让这个在 \(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;
}
posted @ 2026-03-12 16:26  lucasincyber  阅读(2)  评论(0)    收藏  举报