[AT2558]Many Moves

题目大意:有$n$个位置$1,2,dots n$;你有两个棋子$A$和$B$,你要进行$q$次操作,第$i$次操作给定一个$x_i$,你要选择一个棋子移动到$x_i$;求两个棋子最小移动的步数之和。

题解:一个$O(n^2)$的$DP$容易想到$f_{i,j}$表示到了第$i$步,另一个棋子在$j$这个位置。

$$f_{i,x_{i-1}}=min{f_{i-1,j}+|x_i-j|}$$

$$f_{i,j}=f_{i-1,j}+|x_i-x_{i-1}|$$

下面一个还好做,可上面一个呢?

可以考虑拆成$jleq x_i$和$j>x_i$来做

$$ herefore f_{i,x_{i-1}} =
egin{cases}
f_{i-1,j}+x_i-jquad(jleq x_i)\
f_{i-1,j}+j-x_iquad(j>x_i)
end{cases}$$

然后发现是区间修改求最小值,可以用线段树来做。

卡点:1.转移时把$x_{i-1}$写成了$x_{i}$

C++ Code:

#include <cstdio>
#include <cstring>
#define maxn 200010
using namespace std;
const long long inf = 0x3f3f3f3f3f3f3f3f;
inline long long min(long long a, long long b) {return a < b ? a : b;}
inline long long abs(long long a) {return a > 0 ? a : -a;}
int n, k, x, y;
int q, last;
long long V[maxn << 2][3], cov[maxn << 2];
void pushdown(int rt) {
	int lc = rt << 1, rc = rt << 1 | 1;
	long long &tmp = cov[rt];
	V[lc][0] += tmp;
	V[lc][1] += tmp;
	V[lc][2] += tmp;
	cov[lc] += tmp;
	V[rc][0] += tmp;
	V[rc][1] += tmp;
	V[rc][2] += tmp;
	cov[rc] += tmp;
	tmp = 0;
}
void update(int rt) {
	int lc = rt << 1, rc = rt << 1 | 1;
	V[rt][0] = min(V[lc][0], V[rc][0]);
	V[rt][1] = min(V[lc][1], V[rc][1]);
	V[rt][2] = min(V[lc][2], V[rc][2]);
}
void add(int rt, int l, int r, int p, long long num) {
	if (l == r) {
		V[rt][0] = num;
		V[rt][1] = num + l;
		V[rt][2] = num - l;
		return ;
	}
	if (cov[rt]) pushdown(rt);
	int mid = l + r >> 1;
	if (p <= mid) add(rt << 1, l, mid, p, num);
	else add(rt << 1 | 1, mid + 1, r, p, num);
	update(rt);
}
void add(long long num, int rt = 1) {
	V[rt][0] += num;
	V[rt][1] += num;
	V[rt][2] += num;
	cov[rt] += num;
}
long long ask(int rt, int l, int r, int L, int R, int op) {
	if (L <= l && R >= r) return V[rt][op];
	pushdown(rt);
	int mid = l + r >> 1;
	long long ans = inf;
	if (L <= mid) ans = ask(rt << 1, l, mid, L, R, op);
	if (R > mid) ans = min(ans, ask(rt << 1 | 1, mid + 1, r, L, R, op));
	return ans;
}
int main() {
	scanf("%d%d%d%d", &n, &k, &x, &y);
	scanf("%d", &q);
	memset(V, 0x3f, sizeof V);
	add(1, 1, n, x, abs(y - q));
	add(1, 1, n, y, abs(x - q));
	while (--k) {
		last = q; scanf("%d", &q);
		long long t0 = ask(1, 1, n, last, last, 0) + abs(q - last);
		long long t1 = ask(1, 1, n, q, n, 1) - q;
		long long t2 = ask(1, 1, n, 1, q, 2) + q;
		long long ans = min(t0, min(t1, t2));
		add(abs(q - last));
		add(1, 1, n, last, ans);
	}
	printf("%lld
", V[1][0]);
	return 0;
}

  

原文地址:https://www.cnblogs.com/Memory-of-winter/p/9466135.html