环己三烯的冬眠舱

天天网抑云,偶尔读点书。

0%

离线查询

好久不见,刚好一个月没更新了。一个月里还发生了蛮多事的,单一个防疫政策就已经大变天了。希望能看到这句话的朋友们都能保护好自己,能晚不早,能阴不阳。


概述

在线离线 可以简单地理解为对于所有的操作是否需要读入完毕

在线算法的要求是,不用先知道所有的操作(如查询、修改等),一边读入一边执行,所有操作之间的独立性比较高。

而离线算法则相反,要求必须先知道所有的操作,再执行操作。这样的话,我们就有机会合理安排操作顺序,以更高的效率完成所有操作。


例题

LeetCode 1697. 检查边长度限制的路径是否存在

给你一个 n 个点组成的无向图边集 edgeList ,其中 edgeList[i] = [ui, vi, disi] 表示点 ui 和点 vi 之间有一条长度为 disi 的边。请注意,两个点之间可能有 超过一条边

给你一个查询数组 queries ,其中 queries[j] = [pj, qj, limitj] ,你的任务是对于每个查询 queries[j] ,判断是否存在从 pjqj 的路径,且这条路径上的每一条边都 严格小于 limitj

请你返回一个 布尔数组 answer ,其中 answer.length == queries.length ,当 queries[j] 的查询结果为 true 时, answerj 个值为 true ,否则为 false

示例 1:

1
2
3
4
5
输入:n = 3, edgeList = [[0,1,2],[1,2,4],[2,0,8],[1,0,16]], queries = [[0,1,2],[0,2,5]]
输出:[false,true]
解释:上图为给定的输入数据。注意到 01 之间有两条重边,分别为 216
对于第一个查询,01 之间没有小于 2 的边,所以我们返回 false
对于第二个查询,有一条路径(0 -> 1 -> 2)两条边都小于 5 ,所以这个查询我们返回 true

示例 2:

1
2
3
输入:n = 5, edgeList = [[0,1,10],[1,2,5],[2,3,9],[3,4,13]], queries = [[0,4,14],[1,4,13]]
输出:[true,false]
解释:上图为给定数据。

题解

我的第一反应是在线查询的思路,就是把每个 query 都当成独立的,然后再写一个函数判断是否可行,而这个函数的算法可能会采用BFS。后来觉得容易TLE,瞄了眼题解发现要用并查集。然后我就闷头写了个并查集,查询时,将边长度小于当前 query 限制距离的边的两个端点 union 起来,发现还是TLE。(有关并查集的内容可以查阅 这篇文章 。)

然后仔细研究了题解,发现了离线查询这东西。因为题目的查询操作都是给定的,所以我们可以根据每个 query的距离限制对 queries 数组进行升序排序,同时也根据边的长度对 edgeList 进行升序排序,再按照新的queries 顺序进行操作。这样,限制距离更小的 query 操作起来就是限制距离更大的 query 的子集,跟在线查询比都不知道省到哪里去了。

代码

C++

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
class Solution {
private:
vector<int> vec;
int find(int a) {
if (vec[a] == a) return a;
else {
int root = find(vec[a]);
vec[a] = root;
return root;
}
}
void uni(int a, int b) {
vec[find(a)] = find(b);
}
public:
vector<bool> distanceLimitedPathsExist(int n, vector<vector<int>>& edgeList, vector<vector<int>>& queries) {
vec = vector<int>(n, 0);
iota(vec.begin(), vec.end(), 0);

vector<bool> res(queries.size(), false);

sort(edgeList.begin(), edgeList.end(), [](vector<int>& e1, vector<int>& e2) {
return e1[2] < e2[2];
});

vector<int> seq(queries.size());
iota(seq.begin(), seq.end(), 0);
sort(seq.begin(), seq.end(), [&queries](int i1, int i2) {
return queries[i1][2] < queries[i2][2];
});

int idx = 0;
for (int i : seq) {
while (idx < edgeList.size() && edgeList[idx][2] < queries[i][2]) {
uni(edgeList[idx][0], edgeList[idx][1]);
idx++;
}
res[i] = (find(queries[i][0]) == find(queries[i][1]));
}
return res;
}
};

Go

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
func distanceLimitedPathsExist(n int, edgeList [][]int, queries [][]int) []bool {
vec := make([]int,n)

for i := range vec {
vec[i] = i
}
var find func(int) int
find = func(a int) int {
if vec[a] != a {
vec[a] = find(vec[a])
}
return vec[a]
}

union := func(a,b int) {
vec[find(a)] = find(b)
}

for i := range queries {
queries[i] = append(queries[i], i)
}

sort.Slice(edgeList,func(i,j int) bool {
return edgeList[i][2] < edgeList[j][2]
})
sort.Slice(queries,func(i,j int) bool {
return queries[i][2] < queries[j][2]
})

res := make([]bool,len(queries))
var idx int = 0
for _,query := range queries {
for idx < len(edgeList) && edgeList[idx][2] < query[2] {
union(edgeList[idx][0],edgeList[idx][1])
idx++
}
res[query[3]] = find(query[0]) == find(query[1])
}

return res
}

有的没的

入坑Golang

投实习的时候发现好多后端都要求用Go开发,于是决定入坑。语法上过了一遍菜鸟教程,然后力扣上题也刷起来了,中等以下的题都是直接用Go写,今天的Hard用C++写了一遍之后再用Go写了一遍。Go的语法就像是C/C++和Python的融合怪,取了二者之精华,还增加了一些C/C++并没有但是很实用很酷炫的特性(比如for … range语法可以同时迭代下标和值),我很中意。但也有很多有些别扭的地方,比如不支持set ,只有 map ,虽然本质上差不多,也可以用 map 实现 set 的功能,但是用起来就是很麻烦。还没内置 queuestack 之类的数据结构,甚至连 minmax 这种简单而又常用的函数都需要自己手搓。STL真是绝绝子好用到跺jiojio。

没事听点歌(Coldplay - Viva La Vida)