diff --git a/content/post/算法题/nju01.md b/content/post/算法题/nju01.md index aad9c3c..7d0d20e 100644 --- a/content/post/算法题/nju01.md +++ b/content/post/算法题/nju01.md @@ -2,6 +2,7 @@ title: 南软/智软2025年开放日机试第1题 description: 南京大学软件学院/智能软件与工程学院开放日机试第1题,图论+并查集问题 date: 2025-07-27T11:59:00+08:00 +lastmod: 2025-07-28T22:19:00+08:00 slug: nju01 # image: helena-hertz-wWZzXlDpMog-unsplash.jpg categories: @@ -25,6 +26,8 @@ math: true - 接下来输入q行,每行两个整数,表示q个问题。如“1 2”则表示一个问题,表示点1和2之间的最少交通费。 - 程序的输出为q行,每行为一个问题的答案。 +注:题中 L 最大为 3 × 10^8 + ## 示例输入 @@ -79,9 +82,9 @@ math: true ```cpp #include #include -#include // std::iota -#include // std::swap, std::min, std::max -#include // std::pair +#include // iota +#include // swap, min, max +#include // pair #include #include @@ -97,7 +100,7 @@ public: count = n; parent.resize(n); sz.resize(n); - std::iota(parent.begin(), parent.end(), 0); // 从0开始连续填充 + iota(parent.begin(), parent.end(), 0); // 从0开始连续填充 sz.assign(n, 1); } int find(int i) @@ -140,22 +143,22 @@ public: int main() { // C++ 标准输入输出加速 - std::ios_base::sync_with_stdio(false); - std::cin.tie(NULL); + ios_base::sync_with_stdio(false); + cin.tie(NULL); int L, m, q; - std::cin >> L >> m >> q; + cin >> L >> m >> q; // --- 步骤 1: 预处理,构建零费用连通分量 --- // DSU对象,大小为L+1以方便使用1-based索引 DSU dsu(L + 1); - vector> chords; + vector> chords; for (int i = 0; i < m; ++i) { int u, v; - std::cin >> u >> v; + cin >> u >> v; // 存储弦,并保证端点有序,方便后续判断 chords.push_back({std::min(u, v), std::max(u, v)}); // 合并弦的两个端点 @@ -186,7 +189,7 @@ int main() // --- 步骤 2: 构建“分量图” --- // 使用 map 将 DSU 的根节点映射到从 0 开始的连续索引 - std::map comp_map; + map comp_map; int comp_idx_counter = 0; for (int i = 1; i <= L; ++i) { int root = dsu.find(i); @@ -220,12 +223,12 @@ int main() for (int i = 0; i < q; ++i) { int s, t; - std::cin >> s >> t; + cin >> s >> t; // 如果起点和终点在同一个分量,费用为0 if (dsu.is_connected(s, t)) { - std::cout << 0 << "\n"; + cout << 0 << "\n"; continue; } @@ -233,7 +236,7 @@ int main() int start_comp_idx = comp_map[dsu.find(s)]; int end_comp_idx = comp_map[dsu.find(t)]; - std::queue> bfs_q; // 存储 {当前分量索引, 距离} + queue> bfs_q; // 存储 {当前分量索引, 距离} vector dist(num_components, -1); // -1表示未访问 bfs_q.push({start_comp_idx, 0}); @@ -246,7 +249,7 @@ int main() if (curr_comp == end_comp_idx) { - std::cout << d << "\n"; + cout << d << "\n"; break; } @@ -263,4 +266,219 @@ int main() return 0; } -``` \ No newline at end of file +``` + +# 补充:优化思路 + +由于题目给的 L 最大达到了 3 × 10^8,`DSU dsu(L + 1)` 创建了两个大小为 L+1 的 vector,当 L = 3\*10^8 时,需要 2 * 3*10^8 * 4 bytes ≈ 2.4 GB 的内存,毫无疑问会爆内存。 + +我们解决这个问题的核心思想是,不需要关心圆上所有的 L 个点。真正影响“免费交通网络”结构和连接性的点,只有那些被明确提到的“关键点”。 + +这些“关键点”包括: + +- 所有 m 条弦的 2*m 个端点。 +- 所有 q 次查询的 2*q 个起点和终点。 + +除此之外的所有其他点,我们都可以看作是“空白”的弧。只要处理这些数量级很小(最多 2m + 2q 个)的关键点,然后计算它们之间的关系就行了。 + +对原算法进行两个关键的改造: + +- 把 DSU 类从基于 std::vector 的实现改为基于 std::map 的实现。map 只会为我们实际接触到的“关键点”分配内存,而不会预先分配一个大小为 L 的巨大数组。 +- 不遍历 1 到 L 的 for 循环,转而只遍历我们收集到的“关键点”,并检查这些关键点与其在圆弧上的前一个点之间的“边界”,看这些边界是否跨越了不同的连通分量。 + +## C++ 代码 + +```cpp +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// -------- 改造后的 DSU 模板 (基于 map) -------- +class MapDSU +{ +public: + map parent; + map sz; + + // find 操作自动为新遇到的点创建,并返回根节点 + int find(int i) { + if (parent.find(i) == parent.end()) { + parent[i] = i; + sz[i] = 1; + return i; + } + if (parent[i] == i) { + return i; + } + return parent[i] = find(parent[i]); + } + + void unite(int a, int b) { + int root_a = find(a); + int root_b = find(b); + if (root_a != root_b) { + if (sz[root_a] < sz[root_b]) { + swap(root_a, root_b); + } + parent[root_b] = root_a; + sz[root_a] += sz[root_b]; + } + } + + bool is_connected(int a, int b) { + // 如果点不存在于map中,说明是孤立点,肯定不连通 + if (parent.find(a) == parent.end() || parent.find(b) == parent.end()) { + return find(a) == find(b); // 让find去初始化 + } + return find(a) == find(b); + } +}; + +int main() +{ + ios_base::sync_with_stdio(false); + cin.tie(NULL); + + int L, m, q; + cin >> L >> m >> q; + + // 收集所有关键点然后并查集 + MapDSU dsu; + vector> chords(m); + set key_points_set; // 使用 set 自动排序和去重 + + for (int i = 0; i < m; ++i) { + cin >> chords[i].first >> chords[i].second; + if (chords[i].first > chords[i].second) { + swap(chords[i].first, chords[i].second); + } + dsu.unite(chords[i].first, chords[i].second); + key_points_set.insert(chords[i].first); + key_points_set.insert(chords[i].second); + } + + // 交叉判断逻辑不变,作用于 MapDSU + for (int i = 0; i < m; ++i) { + for (int j = i + 1; j < m; ++j) { + int u1 = chords[i].first, v1 = chords[i].second; + int u2 = chords[j].first, v2 = chords[j].second; + if ((u1 < u2 && u2 < v1 && v1 < v2) || (u2 < u1 && u1 < v2 && v2 < v1)) { + dsu.unite(u1, u2); + } + } + } + + // 将查询点也加入关键点集合 + vector> queries(q); + for (int i = 0; i < q; ++i) { + cin >> queries[i].first >> queries[i].second; + key_points_set.insert(queries[i].first); + key_points_set.insert(queries[i].second); + } + + // 将 set 转换为 vector,方便索引 + vector key_points(key_points_set.begin(), key_points_set.end()); + + // 建优化后的分量图 + map comp_map; + int comp_idx_counter = 0; + + // 只要遍历关键点来找所有连通分量 + for (int point : key_points) { + int root = dsu.find(point); + if (comp_map.find(root) == comp_map.end()) { + comp_map[root] = comp_idx_counter++; + } + } + + int num_components = comp_map.size(); + vector> comp_adj(num_components); + + // 关键!!!只检查关键点形成的“边界” + for (int p : key_points) { + // 检查 p 和它在圆上的前一个点 p_prev + int p_prev = (p == 1) ? L : p - 1; + } + // 遍历所有排序后的关键点,检查相邻关键点之间的连接 + for (size_t i = 0; i < key_points.size(); ++i) { + int p1 = key_points[i]; + int p2 = key_points[(i + 1) % key_points.size()]; + + if (!dsu.is_connected(p1, p2)) { + // 这两个关键点之间的弧,连了两个不同的分量 + int root1 = dsu.find(p1), root2 = dsu.find(p2); + if (comp_map.count(root1) && comp_map.count(root2)) { + int idx1 = comp_map[root1], idx2 = comp_map[root2]; + if(idx1 != idx2){ + comp_adj[idx1].push_back(idx2); + comp_adj[idx2].push_back(idx1); + } + } + } + } + + + for (int i = 0; i < q; ++i) { + int s = queries[i].first; + int t = queries[i].second; + + if (dsu.is_connected(s, t)) { + cout << 0 << "\n"; + continue; + } + + // 检查查询点是否在我们的DSU中,如果不在说明它们是孤立点 + if(dsu.parent.find(s) == dsu.parent.end() || dsu.parent.find(t) == dsu.parent.end()){ + } + + int start_comp_idx = comp_map[dsu.find(s)]; + int end_comp_idx = comp_map[dsu.find(t)]; + + queue> bfs_q; + vector dist(num_components, -1); + + bfs_q.push({start_comp_idx, 0}); + dist[start_comp_idx] = 0; + + bool found = false; + while (!bfs_q.empty()) { + auto [curr_comp, d] = bfs_q.front(); + bfs_q.pop(); + + if (curr_comp == end_comp_idx) { + cout << d << "\n"; + found = true; + break; + } + + for (int neighbor_comp : comp_adj[curr_comp]) { + if (dist[neighbor_comp] == -1) { + dist[neighbor_comp] = d + 1; + bfs_q.push({neighbor_comp, d + 1}); + } + } + } + if(!found) { + // 如果BFS没找到,说明分量图不连通 + // 此时距离就是它们在圆弧上的最短距离 + // 备用,以防分量图构建不完全 + int dist_arc = abs(s - t); + cout << min(dist_arc, L - dist_arc) << "\n"; + } + } + + return 0; +} +``` + + + + +