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;
}
posted @ 2025-03-30 18:50  fufuaifufu  阅读(51)  评论(0)    收藏  举报