Dyd's Blog

He who has a strong enough why can bear almost any how.

luoguP4768 [NOI2018] 归程

关于 spfa ……

归程

题意

给定 $n \le 2 \times 10^5$ 个点的图,每条边有海拔 $h_i$ 和长度 $l$ ,有 $q \le 4 \times 10^5$ 次询问,每次给定 $v, p$ ,询问只保留 $h_i \ge p$ 的边的情况下,从 $v$ 出发能走到的点 $u$ 中到点 $1$ 距离最小的点,输出最小距离,强制在线

关于 spfa

一位巨佬用血的教训告诉我们,它死了

所以,我们用 dijkstra 求出点 $1$ 到每个点的最短距离,记作 $dis_i$ ,时间为 $O(m \log n)$

kruskal 重构树

好像说还有可持久化并查集的做法,但我不会,所以用 kruskal 重构树

考虑我们 kruskal 的过程,每次我们取出边权最小的边,用并查集判断两边的联通性,如果不联通就合并并查集(相当于选边)

在以上过程中,因为构造最小生成树过程中树的形状和中途选边的顺序对我们来说不重要,所以我们放弃了这些信息,用路径压缩优化了时间

但现在,如果我们要保留这些信息呢?具体的:

  1. 把所有边排序,记边为 $(x, y, z)$ ,表示 $x \to y$ ,权为 $z$
  2. 构建 $n$ 个无权点,没有边
  3. 每次取出一条边 $(x, y, z)$ :
    • 若 $x, y$ 联通(在同一棵树),不管它
    • 否则,新建一个节点 $t$ ,记 $x, y$ 所在树的根为 $r_x, r_y$ ,让 $t$ 的左右儿子分别为 $r_x, r_y$ ,并让 $t$ 的点权为 $z$

不难发现 kruskal 重构树有如下性质:

  1. 它是一棵二叉树(更进一步,它其实就是个二叉堆),树上除叶子结点以外的点都对应着原来生成树中的边,叶子结点就是原来生成树上的节点
  2. 除叶节点外,儿子节点对应边一定排序在父亲前面,即从每个点到根节点上除叶子结点外按顺序访问到的点的点权是单调的
  3. 对于叶节点 $x, y$ , $lca(x, y)$ 对应的边就是最小生成树中联通 $x, y$ 的“瓶颈边”(它排序在最后)

kruskal 重构树的构建可以在 kruskal 的基础上进行,时间 $O(m \log m)$

思路

那么,我们如何用重构树做本题呢?

首先,我们把边按 $h_i$ 降序排序(边的长度 $l$ 在我们求出 $dis$ 数组后就没有用了),然后建出重构树

由重构树的性质,我们知道,对于点 $x, y$ , $lca(x, y)$ 对应的边就是连接 $x, y$ 的边中海拔最小的,那么对于每次询问 $v$ ,我们就是要找到所有的 $u$ 满足 $h_{lca(u, v)} \ge p$ ,答案就是 $\min(dis_u)$

但直接每个点找肯定是不行的,根据重构树的性质, $x$ 子树中的所有点(除了叶子)的海拔都高于 $x$ ,所以如果 $h_x \ge p$ ,那么它的子树一定都高于 $p$ ,我们可以用树上倍增找到 $v$ 的祖宗中最浅的(即子树最大的)满足 $h_x \ge p$ 的 $x$ ,它子树中的任意一个点 $u$ 都是满足 $h_{lca(u, v)} \ge p$ 的,且不在它子树里面的点一定不满足(因为那些点和 $v$ 的 $lca$ 只会在 $x$ 的上面),那么问题就化为了子树 $\min$ ,预先统计即可

时间 $O(T(m + q) \log m)$

代码

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
#include <bits/stdc++.h>
#define se second
using PII = std::pair<int, int>;
const int N = 2e5 + 5, M = 4e5 + 5, D = 30;
int n, m;
int dis[N], h[N], idx, fa[N + M], f[D][N + M];
bool vis[N];
struct Edge1{ int ne, ver, w; } e[M << 1];
struct Edge2{ int u, v, h; } a[M];
struct Node{ int val, mn; } nd[N + M];
void add(int x, int y, int z){ e[idx] = {h[x], y, z}, h[x] = idx++; }
void dij(int st)
{
memset(vis, false, sizeof vis);
memset(dis, 0x3f, sizeof dis);
std::priority_queue<PII> q;
for (q.push({dis[st] = 0, st}); !q.empty(); )
{
int x = q.top().se; q.pop();
if (vis[x]) continue;
vis[x] = true;
for (int i = h[x], y; ~i; i = e[i].ne)
{
if (vis[y = e[i].ver]) continue;
if (dis[y] > dis[x] + e[i].w && (dis[y] = dis[x] + e[i].w)) q.push({-dis[y], y});
}
}
}
int get(int x){ return x == fa[x] ? x : fa[x] = get(fa[x]); }
void krukal()
{
std::sort(a + 1, a + m + 1, [&](Edge2 x, Edge2 y){ return x.h > y.h; });
int cnt = 0;
for (int i = 1; i <= n; ++i) fa[i] = i, nd[++cnt] = {0, dis[i]};
for (int i = 1; i <= m; ++i)
{
int u = get(a[i].u), v = get(a[i].v);
if (u == v) continue;
nd[++cnt] = {a[i].h, std::min(nd[u].mn, nd[v].mn)};
fa[cnt] = fa[u] = fa[v] = cnt;
f[0][u] = f[0][v] = cnt;
}
for (int j = 1; j < D; ++j)
for (int i = 1; i <= cnt; ++i) f[j][i] = f[j - 1][f[j - 1][i]];
}
int ask(int x, int p)
{
for (int i = D - 1; ~i; --i) if (f[i][x] && nd[f[i][x]].val > p) x = f[i][x];
return nd[x].mn;
}
int main()
{
int T, Q, v, p, K, S, last;
for (scanf("%d", &T); T--; )
{
scanf("%d %d", &n, &m);
memset(h, -1, sizeof h), idx = last = 0;
memset(f, 0, sizeof f);
for (int i = 1, t; i <= m; ++i)
{
scanf("%d %d %d %d", &a[i].u, &a[i].v, &t, &a[i].h);
add(a[i].u, a[i].v, t), add(a[i].v, a[i].u, t);
}
dij(1), krukal();
for (scanf("%d %d %d", &Q, &K, &S); Q--; )
{
scanf("%d %d", &v, &p), v = (v + K * last - 1) % n + 1, p = (p + K * last) % (S + 1);
printf("%d\n", last = ask(v, p));
}
}
return 0;
}