当前位置:首页 > 其他 > NOI 2001密码锁(洛谷P2024)解题全解析:并查集+关系标记算法实战

NOI 2001密码锁(洛谷P2024)解题全解析:并查集+关系标记算法实战

1天前

NOI 2001密码锁(洛谷P2024)解题全解析:并查集+关系标记算法实战 2001 密码锁 并查集 关系标记 算法优化 第1张

一、题目解读

2001年NOI密码锁洛谷P2024)是一个经典的逻辑判断问题。题目描述了一个动物园中动物之间的关系,需要处理两种类型的陈述:同类关系(1)和捕食关系(2)。要求根据给定的K条关系,判断是否存在矛盾,并统计虚假陈述的数量。问题的核心在于如何高效地维护动态关系网络,并快速检测循环中的逻辑冲突。

二、解题思路

本题的突破点在于利用带关系标记并查集(Disjoint Set Union with Relation Marking)。传统并查集通过合并集合判断连通性,但本题需要额外记录节点间的相对关系(同类、捕食)。因此,我们为每个节点引入关系值(0表示同类,1表示吃父节点,2表示被父节点吃),并在路径压缩时动态更新关系。通过维护关系的一致性,即可在合并时检测矛盾。

三、解题步骤

1. 初始化并查集:创建UnionFind类,初始化parent数组(每个节点指向自身)、rank数组(记录集合深度)、relation数组(初始关系均为0)。

2. 处理输入:遍历K条关系,对每条(x, y, type)进行合法性检查(如x、y是否越界,type=2时x≠y)。

3. 合并集合:

    通过find()函数查找x和y的根节点,并路径压缩。

    若根节点相同,则检查关系是否冲突(如x与y的关系与type是否矛盾)。

    若根节点不同,按秩合并,根据type计算关系偏移量并更新relation值。

4. 统计虚假陈述:当检测到矛盾时,falseCount++。

5. 输出结果:最终输出falseCount。

四、代码与注释

#include <iostream>
#include <vector>
using namespace std;

const int MAXN = 50010;

class UnionFind {
private:
    vector<int> parent;
    vector<int> rank;
    vector<int> relation; // 0:同类, 1:吃父节点, 2:被父节点吃
    
public:
    UnionFind(int n) : parent(n+1), rank(n+1, 0), relation(n+1, 0) {
        for(int i = 0; i <= n; ++i) {
            parent[i] = i; // 初始化每个节点为独立集合
        }
    }
    
    // 查找根节点并路径压缩
    int find(int x) {
        if(parent[x]!= x) {
            int orig_parent = parent[x];
            parent[x] = find(parent[x]); // 递归查找根节点
            relation[x] = (relation[x] + relation[orig_parent]) % 3; // 路径压缩时更新关系值
        }
        return parent[x];
    }
    
    // 合并两个集合
    bool unite(int x, int y, int type) {
        int rootX = find(x);
        int rootY = find(y);
        
        if(rootX == rootY) {
            // 检查关系是否冲突
            if((relation[x] - relation[y] + 3) % 3!= type) // 根据相对关系判断矛盾
                return false;
            return true;
        }
        
        // 按秩合并
        if(rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
            relation[rootY] = (relation[x] - relation[y] - type + 6) % 3; // 计算新关系值
        } else {
            parent[rootX] = rootY;
            relation[rootX] = (relation[y] - relation[x] + type + 3) % 3;
            if(rank[rootX] == rank[rootY]) {
                rank[rootY]++;
            }
        }
        return true;
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int N, K;
    cin >> N >> K;
    
    UnionFind uf(N);
    int falseCount = 0;
    
    for(int i = 0; i < K; ++i) {
        int type, x, y;
        cin >> type >> x >> y;
        
        // 检查输入合法性(越界或自吃)
        if(x > N || y > N || (type == 2 && x == y)) {
            falseCount++;
            continue;
        }
        
        // 转换关系类型:1->0(同类), 2->1(捕食)
        int rel = (type == 1)? 0 : 1; 
        if(!uf.unite(x, y, rel)) { // 若合并时检测到矛盾,统计虚假
            falseCount++;
        }
    }
    
    cout << falseCount << endl;
}

五、总结

通过并查集结合关系标记,将逻辑判断问题转化为集合合并与关系验证,巧妙解决了动态关系网络的维护与冲突检测。路径压缩优化了查询效率,而关系值的循环计算(取模3)避免了溢出问题。此思路对类似食物链、传递性关系问题具有通用性,是算法竞赛中处理复杂关系的重要技巧。

原创内容 转载请注明出处

分享给朋友:

相关文章

2023年GESP五级题「因式分解」洛谷B3871算法解析与代码实现

2023年GESP五级题「因式分解」洛谷B3871算法解析与代码实现

一、题目解读2023年GESP五级题「因式分解」(洛谷B3871)要求将给定的大整数N分解为质因数的乘积形式,并输出每个质因数及其指数。题目考察核心算法为质因数分解,需高效处理大数分解,避免超时。难点...

力扣2771题解析:双数组动态规划求解最长非递减子数组问题

力扣2771题解析:双数组动态规划求解最长非递减子数组问题

一、题目解读力扣2771题要求从两个整数数组nums1和nums2中合并元素,寻找最长非递减子数组的长度。非递减子数组指元素单调递增或相等的连续序列。题目难点在于需同时考虑两个数组的交互关系,找到全局...

【蓝桥杯省赛】2014年A组波动数列题解:动态规划解法与代码解析(C++实现)

【蓝桥杯省赛】2014年A组波动数列题解:动态规划解法与代码解析(C++实现)

一、题目解读“波动数列”是2014年蓝桥杯省赛A组的一道经典题目。题目要求生成一个数列,其前n项的和模n等于给定值s,且每项只能通过加减固定值a、b波动。问题本质是求解满足特定模运算条件的数列组合方案...

洛谷1363题解法:虚拟坐标优化BFS解决循环迷宫问题(附代码详解)

洛谷1363题解法:虚拟坐标优化BFS解决循环迷宫问题(附代码详解)

一、题目解读洛谷1363题要求判断在给定大小的循环迷宫中,从起点出发是否可以通过移动到达多个不同的路径方向。迷宫由字符矩阵表示,'S'为起点,'#'为障碍物,其余为可通...

力扣2085题解析:统计两个数组中只出现一次的公共单词数目详解

力扣2085题解析:统计两个数组中只出现一次的公共单词数目详解

一、题目解读力扣2085题要求给定两个字符串数组words1和words2,统计两个数组中只出现一次的公共单词的数量。例如,若words1包含["a", "b"...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。