[AGC047F] Rooks 题解

Description

NN 个车在一张无限大的棋盘上,第 ii 个在 (Xi,Yi)(X_i,Y_i)。每行每列最多一个车。

有一个卒,会替换第 ss 个车,可以走八连通,但是不能走到被车攻击的地方。吃车的时候可以走对角,否则只能走上下左右。

对于 s=1Ns=1\ldots N,求出吃掉最多的车时的最小步数。

2N2×105,1Xi,Yi106,XiXj,YiYj2\leq N\leq 2\times 10^5,1\leq X_i,Y_i\leq 10^6,X_i\neq X_j,Y_i\neq Y_j

Solution

先对 x,yx,y 分别离散化并按照 xx 坐标从小到大排序,那么 ss 任何时刻吃的车一定构成一个区间 [l,r][l,r],满足 lsrl\leq s\leq r

显然可以进行 dp。

fl,r,0/1f_{l,r,0/1} 表示当前吃掉的车为 [l,r][l,r],且当前的位置是 l/rl/r,到最终状态的最小步数。

容易发现任意时刻吃掉车的 yy 坐标也构成一个区间 [ly,ry][l_y,r_y],所以下一步走的车坐标一定为 (l1,ly1),(l1,ry+1),(r+1,ly1),(r+1,ry+1)(l-1,l_y-1),(l-1,r_y+1),(r+1,l_y-1),(r+1,r_y+1)。具体的,如果 yl1=ly1y_{l-1}=l_y-1,可以得到转移方程:fl,r,0fl1,r,0+dis(l1,l)1f_{l,r,0}\leftarrow f_{l-1,r,0}+\text{dis}(l-1,l)-1,剩下的同理。

这个做法的时间复杂度是 O(n2)O(n^2),过不了。


注意到这个做法的转移是 O(1)O(1) 的,而状态很多,所以考虑优化状态。

首先对于 yi=iy_i=i 的问题,一定是先从 ss 走到 11 再走到 nn,或者先到 nn 再一路到 11

这里也是一样的,如果先走到 l1l-1yl2yl1=1|y_{l-2}-y_{l-1}|=1,那么一路向左走直到差不是 11 了一定更优。

考虑把这些相邻差为 11 的段缩起来,容易发现到达段内的任何一个点后段里的其它点也可以走过去。并且如果第一次到了这个段的左右端点,则之后一路把整段走完一定最优。

所以每次拓展时按照整段拓展即可,可以发现现在状态数减少了不少,实际状态上变成 O(n)O(n) 的了。

证明就考虑有用的状态一定满足区间内 yy 构成一段区间且不能拓展,形如下面的图(图源:link):

然后对于每个 yy 相差 11 的极长连续段 [l,r][l,r],计算有多少个状态满足最后一次是从它拓展的。

由于 [l,r][l,r] 极长,所以只有两种包含它的方案:

所以总状态数是 O(n)O(n) 的。

用一个哈希表存下这些状态即可。

时间复杂度:O(nlogn)O(n\log n)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <bits/stdc++.h>

#define int int64_t

const int kMaxN = 2e5 + 5;

struct custom_hash {
static uint64_t splitmix64(uint64_t x) {
x += 0x9e3779b97f4a7c15;
x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
return x ^ (x >> 31);
}
size_t operator()(uint64_t x) const {
static const uint64_t FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(x + FIXED_RANDOM);
}
size_t operator()(std::tuple<uint64_t, uint64_t, uint64_t> x) const {
static const uint64_t FIXED_RANDOM = std::chrono::steady_clock::now().time_since_epoch().count();
return splitmix64(std::get<0>(x) + FIXED_RANDOM) ^
(splitmix64(std::get<1>(x) + FIXED_RANDOM) >> 1) ^
(splitmix64(std::get<2>(x) + FIXED_RANDOM) >> 2);
}
};

int n;
int x[kMaxN], y[kMaxN], unqx[kMaxN], unqy[kMaxN], ans[kMaxN];
int L[kMaxN], R[kMaxN];
std::unordered_map<std::tuple<int, int, bool>, int, custom_hash> f;

struct Node {
int x, y, id;
} t[kMaxN];

int getdis(int i, int j) {
return abs(unqx[t[i].x] - unqx[t[j].x]) + abs(unqy[t[i].y] - unqy[t[j].y]);
}

int solve(int l, int r, int ly, int ry, bool isleft) {
if (f.count({l, r, isleft})) return f[{l, r, isleft}];
int ret = 1e18;
if (l > 1 && (t[l - 1].y == ly - 1 || t[l - 1].y == ry + 1))
ret = std::min(ret, solve(L[l - 1], r, std::min({ly, t[l - 1].y, t[L[l - 1]].y}), std::max({ry, t[l - 1].y, t[L[l - 1]].y}), 1) + getdis(l - 1, isleft ? l : r) + getdis(l - 1, L[l - 1]));
if (r < n && (t[r + 1].y == ly - 1 || t[r + 1].y == ry + 1))
ret = std::min(ret, solve(l, R[r + 1], std::min({ly, t[r + 1].y, t[R[r + 1]].y}), std::max({ry, t[r + 1].y, t[R[r + 1]].y}), 0) + getdis(r + 1, isleft ? l : r) + getdis(r + 1, R[r + 1]));
if (ret == 1e18) ret = -(r - l);
return f[{l, r, isleft}] = ret;
}

void dickdreamer() {
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> x[i] >> y[i];
unqx[i] = x[i], unqy[i] = y[i];
}
std::sort(unqx + 1, unqx + 1 + n);
std::sort(unqy + 1, unqy + 1 + n);
for (int i = 1; i <= n; ++i) {
x[i] = std::lower_bound(unqx + 1, unqx + 1 + n, x[i]) - unqx;
y[i] = std::lower_bound(unqy + 1, unqy + 1 + n, y[i]) - unqy;
t[i] = {x[i], y[i], i};
}
std::sort(t + 1, t + 1 + n, [&] (auto a, auto b) { return a.x < b.x; });
for (int i = 1; i <= n; ++i) {
if (i > 1 && abs(t[i].y - t[i - 1].y) == 1) L[i] = L[i - 1];
else L[i] = i;
}
for (int i = n; i; --i) {
if (i < n && abs(t[i].y - t[i + 1].y) == 1) R[i] = R[i + 1];
else R[i] = i;
}
for (int i = 1; i <= n; ++i) ans[t[i].id] = solve(i, i, t[i].y, t[i].y, 1);
for (int i = 1; i <= n; ++i) std::cout << ans[i] << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

[AGC047F] Rooks 题解
https://sobaliuziao.github.io/2025/04/07/post/aee62492.html
作者
Egg_laying_master
发布于
2025年4月7日
许可协议