力扣周赛 | 力扣第409场周赛
本文将对力扣第409场周赛的T3和T4进行讲解:新增道路查询后的最短距离II、交替组III,涉及最短路、广度优先搜索、树状数组等知识点。
T3 - 新增道路查询后的最短距离 II
题目链接:100376. 新增道路查询后的最短距离 II。
题目描述
给你一个整数
有
所有查询中不会存在两个查询都满足
返回一个数组
数据范围
- 查询中不存在重复的道路。
- 不存在两个查询都满足
且 。
解题思路
数据范围中的最后一个条件实则在提醒,区间是可以很容易合并的,因为不存在严格相交的两个区间。
初始有
- 若
中存在位于 之间的区间,则找出这样的区间,将其移出集合 ,然后合并这些区间,合并结果即为 ,将区间 加入集合 。此时集合大小即为最短路径的长度。 - 若
中不存在位于 之间的区间,说明区间 必定被 中某个区间包含,也即,询问 不影响最短路径的长度,此时无需进行任何操作,集合大小即为最短路径的长度。
考虑到初始时,区间是完整的,即,集合
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;
}
时间复杂度:
空间复杂度:
T4 - 交替组 III
题目链接:100388. 交替组 III
题目描述
给你一个整数数组
表示第 块瓷砖的颜色是 红色 。 表示第 块瓷砖的颜色是 蓝色 。
环中连续若干块瓷砖的颜色如果是 交替 颜色(也就是说这组瓷砖中除了第一块和最后一块瓷砖以外,中间瓷砖的颜色与它 左边 和 右边 的颜色都不同),那么它被称为一个 交替组。
你需要处理两种类型的查询:
,确定大小为 的 交替组 的数量。 ,将 更改为 。
返回数组
注意 ,由于
数据范围
或- 对于所有的
: : , : , ,
解题思路
考虑长度为
将换划分为若干个交替组,要求交替组数量尽可能少。令
通常情况下,“长度大于等于
“长度小于等于
对于修改操作,要么会导致交替组合并,要么会导致交替组拆分,所以需要维护交替组集合。由于所有交替组首尾相连可以组成环,所以,只需要记录每个交替组的右端点即可(左端点也可)。
综上,每次修改操作,先对交替组进行合并或者拆分,在根据交替组处理结果对树状数组进行修改,修改操作大体思路如下:
- 先通过
对交替组进行拆分/合并; 中调用中间函数 对树状数组进行更新; 实际调用树状数组模板函数 对树状数组进行更新。
代码实现
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;
}
时间复杂度:
空间复杂度:
END
以上就是本文的全部内容,如果觉得有一点点帮助,欢迎点赞、转发加关注,如果有任何问题,欢迎在评论区交流和讨论,咱们下期见!
文章声明:题目来源 力扣 平台,如有侵权,请联系删除!
题目来源:力扣第409场双周赛。
文章文档:公众号 字节幺零二四
回复关键字可获取本文文档。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 一文彻底搞懂 MCP:AI 大模型的标准化工具箱
· 电商平台中订单未支付过期如何实现自动关单?
· 用 .NET NativeAOT 构建完全 distroless 的静态链接应用
· 精选 4 款免费且实用的数据库管理工具,程序员必备!
· Cursor:一个让程序员“失业”的AI代码搭子
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
· 重生之我是操作系统(七)----内存管理(上)
· .NET 阻止关机机制以及关机前执行业务