AtCoder Beginner Contest 399(c,d)
比赛链接
c题目描述
给定一个具有 N 个顶点和 M 条边的简单无向图,顶点编号从 1 到 N,第 i 条边连接顶点 (u_i) 和 (v_i)。需要计算从该图中删除最少多少条边,才能使图变成一个森林(即不包含任何环的简单无向图)。
代码思路
本题使用并查集(Disjoint Set Union,DSU)来解决。并查集是一种用于处理不相交集合的合并与查询问题的数据结构,非常适合用于判断图中是否存在环。具体思路如下:
初始化并查集:为每个顶点创建一个独立的集合,每个集合的父节点初始化为自身。
遍历每条边:对于每一条边 ((u, v)),检查 u 和 v 是否属于同一个集合。
- 如果 u 和 v 不属于同一个集合,说明这条边不会形成环,将这两个集合合并。
- 如果 u 和 v 属于同一个集合,说明这条边会形成环,需要将其删除,记录删除边的数量。
输出结果:遍历完所有边后,输出需要删除的边的数量。代码实现
#include <iostream>
#include <vector>
using namespace std;
// 查找元素 x 所在集合的根节点,并进行路径压缩
int find(int x, vector<int>& parent) {
if (parent[x] != x) {
parent[x] = find(parent[x], parent); // 路径压缩
}
return parent[x];
}
// 合并元素 x 和 y 所在的集合
void unionSet(int x, int y, vector<int>& parent, vector<int>& rank) {
int rootX = find(x, parent);
int rootY = find(y, parent);
if (rootX != rootY) {
if (rank[rootX] > rank[rootY]) {
parent[rootY] = rootX;
} else if (rank[rootX] < rank[rootY]) {
parent[rootX] = rootY;
} else {
parent[rootY] = rootX;
rank[rootX]++;
}
}
}
int main() {
int N, M;
cin >> N >> M;
vector<int> parent(N + 1), rank(N + 1, 0);
for (int i = 1; i <= N; i++) {
parent[i] = i; // 初始化并查集
}
int deleteCount = 0;
for (int i = 0; i < M; i++) {
int u, v;
cin >> u >> v;
if (find(u, parent) != find(v, parent)) {
unionSet(u, v, parent, rank);
} else {
deleteCount++; // 如果已连接,说明是环,记录删除
}
}
cout << deleteCount << endl;
return 0;
}
d问题分析
我们需要找到满足以下条件的整数对 (a, b) 的数量:
a 和 b 的两个出现位置在原数组中均不相邻。
通过交换 a 和 b 的位置,可以使 a 和 b 的两个出现位置均相邻。
关键观察:当且仅当 a 和 b 的两个出现位置形成两个独立的相邻对时,交换操作可以满足条件。例如,位置序列为 ...a, b, ..., a, b... 或 ...a, b, ..., b, a...,此时交换后 a 和 b 的两个位置均相邻。
方法思路
预处理位置:记录每个数字的两个出现位置。
遍历相邻元素:对于每对相邻元素 (a, b),检查它们的位置是否满足条件。
条件验证:收集四个位置并排序,检查是否形成两个独立的相邻区间。
去重存储:使用集合存储有效对,避免重复计数。
解决代码
#include <algorithm>
#include <iostream>
#include <set>
#include <vector>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int N;
cin >> N;
vector<int> A(2 * N);
for (auto& a : A) cin >> a;
vector<vector<int>> position(N + 1);
for (int i = 0; i < 2 * N; i++) {
position[A[i]].push_back(i);
}
set<pair<int, int>> answers;
for (int i = 0; i < 2 * N - 1; i++) {
int a = A[i], b = A[i + 1];
if (a == b) continue;
// 检查a和b的两个位置是否不相邻
if (position[a][0] + 1 == position[a][1] ||
position[b][0] + 1 == position[b][1]) {
continue;
}
// 收集四个位置并排序
vector<int> v{position[a][0], position[a][1],
position[b][0], position[b][1]};
sort(v.begin(), v.end());
// 检查是否满足条件
if (v[0] + 1 == v[1] && v[2] + 1 == v[3]) {
answers.emplace(min(a, b), max(a, b));
}
}
cout << answers.size() << "\n";
}
return 0;
}