本文将对力扣第409场周赛的T3和T4进行讲解:新增道路查询后的最短距离II、交替组III,涉及最短路、广度优先搜索、树状数组等知识点。
T3 - 新增道路查询后的最短距离 II
题目链接:100376. 新增道路查询后的最短距离 II。
题目描述
给你一个整数 n n n 和一个二维整数数组 q u e r i e s queries queries。
有 n n n 个城市,编号从 0 0 0 到 n − 1 n - 1 n−1。初始时,每个城市 i i i 都有一条单向道路通往城市 i + 1 i + 1 i+1( 0 < = i < n − 1 0 <= i < n - 1 0<=i<n−1)。
q u e r i e s [ i ] = [ u i , v i ] queries[i] = [ui, vi] queries[i]=[ui,vi] 表示新建一条从城市 u i ui ui 到城市 v i vi vi 的单向道路。每次查询后,你需要找到从城市 0 0 0 到城市 n − 1 n - 1 n−1 的最短路径的长度。
所有查询中不会存在两个查询都满足 q u e r i e s [ i ] [ 0 ] < q u e r i e s [ j ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < q u e r i e s [ j ] [ 1 ] queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1] queries[i][0]<queries[j][0]<queries[i][1]<queries[j][1]。
返回一个数组 a n s w e r answer answer,对于范围 [ 0 , q u e r i e s . l e n g t h − 1 ] [0, queries.length - 1] [0,queries.length−1] 中的每个 i i i, a n s w e r [ i ] answer[i] answer[i] 是处理完前 i + 1 i + 1 i+1 个查询后,从城市 0 0 0 到城市 n − 1 n - 1 n−1 的最短路径的长度。
数据范围
- 3 < = n < = 105 3 <= n <= 105 3<=n<=105
- 1 < = q u e r i e s . l e n g t h < = 105 1 <= queries.length <= 105 1<=queries.length<=105
- q u e r i e s [ i ] . l e n g t h = = 2 queries[i].length == 2 queries[i].length==2
- 0 < = q u e r i e s [ i ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < n 0 <= queries[i][0] < queries[i][1] < n 0<=queries[i][0]<queries[i][1]<n
- 1 < q u e r i e s [ i ] [ 1 ] − q u e r i e s [ i ] [ 0 ] 1 < queries[i][1] - queries[i][0] 1<queries[i][1]−queries[i][0]
- 查询中不存在重复的道路。
- 不存在两个查询都满足 i ! = j i != j i!=j 且 q u e r i e s [ i ] [ 0 ] < q u e r i e s [ j ] [ 0 ] < q u e r i e s [ i ] [ 1 ] < q u e r i e s [ j ] [ 1 ] queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1] queries[i][0]<queries[j][0]<queries[i][1]<queries[j][1]。
解题思路
数据范围中的最后一个条件实则在提醒,区间是可以很容易合并的,因为不存在严格相交的两个区间。
初始有 [ 0 , 1 ] , [ 1 , 2 ] , . . . , [ n − 2 , n − 1 ] [0,1],[1,2],...,[n-2,n-1] [0,1],[1,2],...,[n−2,n−1] 共 n n n 个区间,将这些区间加入区间集合 s s s。依次取出每个询问 ( u , v ) (u,v) (u,v)
- 若 s s s 中存在位于 [ u , v ] [u,v] [u,v] 之间的区间,则找出这样的区间,将其移出集合 s s s,然后合并这些区间,合并结果即为 [ u , v ] [u,v] [u,v],将区间 [ u , v ] [u,v] [u,v] 加入集合 s s s。此时集合大小即为最短路径的长度。
- 若 s s s 中不存在位于 [ u , v ] [u,v] [u,v] 之间的区间,说明区间 ( u , v ) (u,v) (u,v) 必定被 s s s 中某个区间包含,也即,询问 ( u , v ) (u,v) (u,v) 不影响最短路径的长度,此时无需进行任何操作,集合大小即为最短路径的长度。
考虑到初始时,区间是完整的,即,集合 s s s 中所有区间的和为 [ , n − 1 ] [,n-1] [,n−1],所以,无论如何合并,在任意时刻,区间之和都是完整的。因此,可使用链表来维护所有区间,每个节点表示一个区间端点。
vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>> &queries) {
int nxt[n];
// 初始区间链
for (int i = 0; i < n; i++)nxt[i] = i + 1;
// 目前的区间数量
int last = n - 1;
vector<int> res;
for (auto &q: queries) {
int u = q[0], v = q[1];
// 存在区间位于区间[u,v]之内
if (nxt[u] != -1 && nxt[v] != -1) {
for (int i = u, t; i != v;) {
// nxt[i]=-1 标记i不再作为区间端点
// last-- 表示区间[i,nxt[i]]被移出
t = nxt[i], nxt[i] = -1, i = t, last--;
}
// nxt[u]=v 表示将合并得到的区间[u,v]加入区间链
// last++ 表示[u,v]加入区间链
nxt[u] = v, last++;
}
// 目前的区间数量即为询问后的最短路径的长度
res.push_back(last);
}
return res;
}
时间复杂度: O ( n + q ) O(n+q) O(n+q)。
空间复杂度: O ( n ) O(n) O(n),返回值不计。
T4 - 交替组 III
题目链接:100388. 交替组 III
题目描述
给你一个整数数组 c o l o r s colors colors 和一个二维整数数组 q u e r i e s queries queries 。 c o l o r s colors colors表示一个由红色和蓝色瓷砖组成的环,第 i i i 块瓷砖的颜色为 c o l o r s [ i ] colors[i] colors[i] :
- c o l o r s [ i ] = = 0 colors[i] == 0 colors[i]==0 表示第 i i i 块瓷砖的颜色是 红色 。
- c o l o r s [ i ] = = 1 colors[i] == 1 colors[i]==1 表示第 i i i 块瓷砖的颜色是 蓝色 。
环中连续若干块瓷砖的颜色如果是 交替 颜色(也就是说这组瓷砖中除了第一块和最后一块瓷砖以外,中间瓷砖的颜色与它 左边 和 右边 的颜色都不同),那么它被称为一个 交替组。
你需要处理两种类型的查询:
- q u e r i e s [ i ] = [ 1 , s i z e i ] queries[i] = [1, sizei] queries[i]=[1,sizei],确定大小为 s i z e i sizei sizei的 交替组 的数量。
- q u e r i e s [ i ] = [ 2 , i n d e x i , c o l o r i ] queries[i] = [2, indexi, colori] queries[i]=[2,indexi,colori],将 c o l o r s [ i n d e x i ] colors[indexi] colors[indexi]更改为 c o l o r i colori colori。
返回数组 a n s w e r answer answer,数组中按顺序包含第一种类型查询的结果。
注意 ,由于 c o l o r s colors colors 表示一个 环 ,第一块 瓷砖和 最后一块 瓷砖是相邻的。
数据范围
- 4 < = c o l o r s . l e n g t h < = 5 ∗ 104 4 <= colors.length <= 5 * 104 4<=colors.length<=5∗104
- 0 < = c o l o r s [ i ] < = 1 0 <= colors[i] <= 1 0<=colors[i]<=1
- 1 < = q u e r i e s . l e n g t h < = 5 ∗ 104 1 <= queries.length <= 5 * 104 1<=queries.length<=5∗104
- q u e r i e s [ i ] [ 0 ] = = 1 queries[i][0] == 1 queries[i][0]==1 或 q u e r i e s [ i ] [ 0 ] = = 2 queries[i][0] == 2 queries[i][0]==2
- 对于所有的
i
i
i:
- q u e r i e s [ i ] [ 0 ] = = 1 queries[i][0] == 1 queries[i][0]==1: q u e r i e s [ i ] . l e n g t h = = 2 queries[i].length == 2 queries[i].length==2, 3 < = q u e r i e s [ i ] [ 1 ] < = c o l o r s . l e n g t h − 1 3 <= queries[i][1] <= colors.length - 1 3<=queries[i][1]<=colors.length−1
- q u e r i e s [ i ] [ 0 ] = = 2 queries[i][0] == 2 queries[i][0]==2: q u e r i e s [ i ] . l e n g t h = = 3 queries[i].length == 3 queries[i].length==3, 0 < = q u e r i e s [ i ] [ 1 ] < = c o l o r s . l e n g t h − 1 0 <= queries[i][1] <= colors.length - 1 0<=queries[i][1]<=colors.length−1, 0 < = q u e r i e s [ i ] [ 2 ] < = 1 0 <= queries[i][2] <= 1 0<=queries[i][2]<=1
解题思路
考虑长度为 a a a 的交替组,若 b ≤ a b≤a b≤a,则共包含 a − b + 1 a-b+1 a−b+1 个长度为 b b b 的交替组。
将换划分为若干个交替组,要求交替组数量尽可能少。令 f [ x ] f[x] f[x] = 长度大于等于 x x x 的交替组的长度之和, g [ x ] g[x] g[x] = 长度大于等于 x x x 的交替组的数量,则,长度为 s s s 的交替组的数量为 f [ x ] − ( s − 1 ) ∗ g [ x ] f[x]-(s-1)*g[x] f[x]−(s−1)∗g[x]。
通常情况下,“长度大于等于 x x x 的交替组的长度之和”会转换为“长度总和”-“长度小于等于 x − 1 x-1 x−1 的交替组的长度之和”, g [ x ] g[x] g[x] 同理。
“长度小于等于 x x x 的交替组的长度之和”可用树状数组维护,“长度小于等于 x x x 的交替组的数量”同理。
对于修改操作,要么会导致交替组合并,要么会导致交替组拆分,所以需要维护交替组集合。由于所有交替组首尾相连可以组成环,所以,只需要记录每个交替组的右端点即可(左端点也可)。
综上,每次修改操作,先对交替组进行合并或者拆分,在根据交替组处理结果对树状数组进行修改,修改操作大体思路如下:
- 先通过 i s t ( ) / d e l ( ) ist()/del() ist()/del() 对交替组进行拆分/合并;
- i s t ( ) / d e l ( ) ist()/del() ist()/del() 中调用中间函数 u p d a t e ( ) update() update() 对树状数组进行更新;
- u p d a t e ( ) update() update() 实际调用树状数组模板函数 a d d ( ) add() add() 对树状数组进行更新。
代码实现
vector<int> numberOfAlternatingGroups(vector<int> &colors, vector<vector<int>> &queries) {
int n = colors.size();
// tr1[i] 表示长度小于或等于i的交替组的数量
// tr2[i] 表示长度小于或等于i的交替组的长度之和
int tr1[n + 1], tr2[n + 1];
memset(tr1, 0, sizeof tr1);
memset(tr2, 0, sizeof tr2);
// 树状数组模板代码
auto add = [&](int *tr, int p, int v) {
for (; p <= n; p += p & -p)tr[p] += v;
};
auto query = [&](int *tr, int p) {
int res = 0;
for (; p; p -= p & -p)res += tr[p];
return res;
};
/**
* v=1 表示新增,v=-1 表示删除
* 新增右端点p,即,将[l+1,r]拆分为[l+1,p]和[p+1,r]两个交替组
* 或者
* 删除右端点p,即,将[l+1,p]和[p+1,r]两个交替组合并为[l+1,r]
*/
auto update = [&](int l, int r, int p, int v) {
// v=1时删除[l+1,r]
// v=-1时新增[l+1,r]
int len = (r - l + n) % n;
// 当仅有一个右端点且新增右端点p时,l和r相等,len为0
if (len == 0)len = n;
add(tr1, len, -v), add(tr2, len, -v * len);
// v=1时新增[l+1,p]和[p+1,r]
// v=-1时删除[l+1,p]和[p+1,r]
// 右端点不重复,故p和l必定不相等,故len必不为0
len = (p - l + n) % n;
add(tr1, len, v), add(tr2, len, v * len);
// 右端点不重复,故p和r必定不相等,故len必不为0
len = (r - p + n) % n;
add(tr1, len, v), add(tr2, len, v * len);
};
// 存储交替组右端点
set<int> st;
// 往st中新增交替组右端点p
auto ist = [&](int p) {
if (st.empty()) {
// 在无右端点的情况下新增一个右端点
// 相当于新增1个长度为n的交替组,长度小于等于n的交替组的总和加n
// 由于此时将端点p添加到st中后,p的前后均没有右端点,故无法走else中的逻辑,需要特殊处理
st.insert(p);
add(tr1, n, 1), add(tr2, n, n);
} else {
// 在有右端点的情况下新增一个右端点
// 相当于将一个交替组拆分成两个交替组
// 即将[l+1,r]拆分为[l+1,p]和[p+1,r]两个交替组
// it指向右端点p
auto it = st.insert(p).first;
int l = it == st.begin() ? *prev(st.end()) : *prev(it);
int r = next(it) == st.end() ? *st.begin() : *next(it);
update(l, r, p, 1);
}
};
// 从st中删除交替组右端点p
auto del = [&](int p) {
if (st.size() == 1) {
// 在仅有1个右端点的情况下删除一个右端点
// 相当于删除1个长度为n的交替组,长度小于等于n的交替组的总和减n
// 由于此时将端点p从st中删除后,st中没有右端点,故无法走else中的逻辑,需要特殊处理
st.erase(st.find(p));
add(tr1, n, -1), add(tr2, n, -n);
} else {
// 在有多个右端点的情况下删除一个右端点
// 相当于将两个交替组合并为一个交替组
// 即将[l+1,p]和[p+1,r]两个交替组合并为[l+1,r]
// it指向右端点p的下一个元素
auto it = st.erase(st.find(p));
int l = it == st.begin() ? *prev(st.end()) : *prev(it);
int r = it == st.end() ? *st.begin() : *it;
update(l, r, p, -1);
}
};
// 记录每个最长交替组的右端点
for (int i = 0; i < n; i++)
if (colors[i] == colors[(i + 1) % n])ist(i);
// 处理每个请求
vector<int> res;
for (auto &q: queries) {
if (q[0] == 1) {
// 查询
if (!st.empty()) {
// 长度大于q[1]的交替组的数量=所有交替组的数量-长度小于等于q[1]-1的交替组的数量
int cnt = query(tr1, n) - query(tr1, q[1] - 1);
// 长度大于q[1]的交替组的总和=所有交替组的总和-长度小于等于q[1]-1的交替组的总和
int total = query(tr2, n) - query(tr2, q[1] - 1);
res.push_back(total - (q[1] - 1) * cnt);
}
// 无右端点的情况并未记录到线段树,故特殊处理
else res.push_back(n);
} else {
// 修改
int p = q[1], c = q[2];
// p的前后位置
int pre = (p - 1 + n) % n, nxt = (p + 1) % n;
// 改前pre和p一样,则先删除右端点pre
if (colors[pre] == colors[p])del(pre);
// 改前p和nxt一样,则先删除右端点p
if (colors[p] == colors[nxt])del(p);
// 修改p的颜色
colors[p] = c;
// 改后pre和p一样,则新增右端点pre
if (colors[pre] == colors[p])ist(pre);
// 改后p和nxt一样,则新增右端点p
if (colors[p] == colors[nxt])ist(p);
// 需要注意的是,改前pre和p一样,改后pre和p可能依旧一样(如改),所以,改前改后得分开处理
// p和nxt同理
}
}
return res;
}
时间复杂度: O ( ( n + q ) l o g ( n ) ) O((n+q)log(n)) O((n+q)log(n))。
空间复杂度: O ( n ) O(n) O(n),返回值不计。
END
以上就是本文的全部内容,如果觉得有一点点帮助,欢迎点赞、转发加关注,如果有任何问题,欢迎在评论区交流和讨论,咱们下期见!
文章声明:题目来源 力扣 平台,如有侵权,请联系删除!
题目来源:力扣第409场双周赛。
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。