只要你跑的够快,锅就追不上你

0%

「APIO 2015」Jakarta Skyscrapers(BFS)

题目大意

「APIO 2015」Jakarta Skyscrapers(Luogu 3645)

有 $n$ 个位置,$m$ 只 doge,第 $i$ 只在位置 $b_i$ 上。一开始给 $1$ 号 doge 传达信息,第 $i$ 只 doge 在收到信息后,可以进行如下操作:

  • 自己向左后向右移动 $p_i$ 个位置,花费 $1$ 个单位时间。
  • 将携带的信息传达给在同一个位置上的 doge,花费 $0$ 个单位时间。

问信息传达给 $2$ 号 doge 所需要的最少时间。

数据范围:$n, m \le 3 \times 10^4$。

思路分析

最容易想到的是一个暴力算法:我们每次让 doge 向左或向右跳,碰到一个 doge 就传达信息。考虑对于每一个位置建一个点,每新来一个 doge 就对于每一个它能够跳到的位置新建一个辅助点。然后:

  • 每个辅助点向对应位置的点连一条长度为 $0$ 的边。
  • 从 $b_i$ 向 $b_i$ 的辅助点连一条长度为 $0$ 的边。
  • 如果 doge 能够跳到位置 $x$,就从 $x$ 的辅助点向 $x - p_i$ 或 $x + p_i$ 的辅助点连长度为 $1$ 的边(取决于 $x$ 和 $b_i$ 的相对位置)。

不幸的是,我们发现这样的点数,边数都是 $O(nm)$ 的,无法通过此题。注意到我们的算法在 $p_i$ 较大的时候不会出现问题,而 $p_i$ 较小的时候的点数,边数较大。具体来讲,我们的点数是不超过 $n + \frac{nm}{P_0}$ 的,边数是不超过 $m + \frac{2mn}{P_0}$ 的,其中 $P_0$ 是所有 $p_i$ 的最小值。

那么,有没有在 $p_i$ 较小的时候可行的算法呢?发现 $p_i$ 较小的时候很多 doge 能跳到的位置集合都是完全相同的。也就是说,如果两个 doge $p_i$ 相同,$b_i \bmod p_i$ 同余,那么我们考虑将它们一起解决。对于每一个位置再建立 $P_1$ 个辅助点,其中 $P_1$ 是所有 $p_i$ 的最大值。我们记第 $i$ 个点的第 $j$ 个辅助点为 $\text{pnt}(i, j)$。建如下的图:

  • 每个 $\text{pnt}(i, j)$ 向 $i$ 点连一条长度为 $0$ 的边。
  • 每个 $\text{pnt}(i, j)$ 向 $\text{pnt}(i - j, j), \text{pnt}(i + j, j)$ 连长度为 $1$ 的边。
  • 对于每个 doge,从 $b_i$ 向 $\text{pnt}(b_i, p_i)$ 连长度为 $0$ 的边。

经过计算,这个图的点数不超过 $n + P_1 \times n$,边数不超过 $2 P_1 \times n + m$。单独来看它还是 $O(nm)$ 的,但是我们可以将其与前一种建图方法结合起来。设定阈值 $\text{Bound}$,如果 $p_i > \text{Bound}$ 就使用第一种建图方法,否则使用第二种建图方法。这样图的点数不超过 $n + \text{Bound} \times n + \frac{nm}{\text{Bound}}$,边数不超过 $2n \times \text{Bound} + \frac{2nm}{\text{Bound}} + m$。发现 $\text{Bound} = \sqrt{m}$ 时最优,建完图后跑最短路即可。当然由于这个图的边权只有 $0, 1$,所以可以直接 BFS。时间复杂度 $O(\sqrt{m}(n + m))$。

代码实现

1
#include <cmath>
2
#include <cstdio>
3
#include <cstring>
4
using namespace std;
5
6
const int maxn = 3e4, maxb = 175, maxv = 1.05e7, maxe = 2.1e7, magic = 1 << 20;
7
int n, m, cnt, bnd, s, t, l, r, Q[magic + 3], pnt[maxb + 3][maxn + 3], tot, msk[maxe + 3], nxt[maxe + 3], lnk[maxv + 3], dist[maxv + 3];
8
bool vis[maxb + 3][maxb + 3];
9
10
void add(int u, int v, int w) {
11
	msk[++tot] = v * 2 + w;
12
	nxt[tot] = lnk[u], lnk[u] = tot;
13
}
14
15
int main() {
16
	scanf("%d %d", &n, &m);
17
	cnt = n, bnd = sqrt(m) + .5;
18
	for (int i = 1, b, p; i <= m; i++) {
19
		scanf("%d %d", &b, &p);
20
		b++;
21
		if (i == 1) s = b;
22
		if (i == 2) t = b;
23
		if (p > bnd) {
24
			int x = ++cnt, u = x, v;
25
			add(b, x, 0);
26
			for (int i = b - p; i >= 1; i -= p) {
27
				v = ++cnt;
28
				add(u, v, 1);
29
				add(v, i, 0);
30
				u = v;
31
			}
32
			u = x;
33
			for (int i = b + p; i <= n; i += p) {
34
				v = ++cnt;
35
				add(u, v, 1);
36
				add(v, i, 0);
37
				u = v;
38
			}
39
		} else {
40
			if (!vis[p][b % p]) {
41
				vis[p][b % p] = 1;
42
				int x = (b - 1) % p + 1;
43
				for (int i = x; i <= n; i += p) {
44
					add(pnt[p][i] = ++cnt, i, 0);
45
					if (i > x) {
46
						add(pnt[p][i], pnt[p][i - p], 1);
47
						add(pnt[p][i - p], pnt[p][i], 1);
48
					}
49
				}
50
			}
51
			add(b, pnt[p][b], 0);
52
		}
53
	}
54
	memset(dist, -1, sizeof(dist));
55
	Q[r++] = s;
56
	dist[s] = 0;
57
	while (l != r) {
58
		int u = Q[l++];
59
		if (l == magic) l = 0;
60
		if (u == t) break;
61
		for (int i = lnk[u], v, w; i; i = nxt[i]) {
62
			v = msk[i] >> 1, w = msk[i] & 1;
63
			if (~dist[v]) continue;
64
			dist[v] = dist[u] + w;
65
			if (w) {
66
				Q[r++] = v;
67
				if (r == magic) r = 0;
68
			} else {
69
				if (--l < 0) l = magic - 1;
70
				Q[l] = v;
71
			}
72
		}
73
	}
74
	printf("%d\n", dist[t]);
75
	return 0;
76
}