找回密码
 立即注册
查看: 5|回复: 0

带权并查集实战:2001年NOI食物链问题详解

[复制链接]
  • 打卡等级:即来则安
  • 打卡总天数:16
  • 打卡月天数:7
  • 打卡总奖励:111
  • 最近打卡:2025-07-12 15:59:47

21

主题

1

回帖

2469

积分

VIP会员

积分
2469
发表于 2025-7-1 11:14:24 | 显示全部楼层 |阅读模式
一、问题背景与算法思路
食物链问题需要维护三种生物关系(同类、捕食、被捕食),判断K条陈述中有多少是假话。这个问题**展现了带权并查集在关系维护中的强大能力。
二、完整代码实现(带详细注释)
  1. #include <iostream>
  2. #include <vector>
  3. using namespACe std;

  4. const int MAXN = 50010;

  5. class UnionFind {
  6. private:
  7.     vector<int> parent;   // 父节点数组
  8.     vector<int> rank;     // 秩数组
  9.     vector<int> relation; // 关系数组:0-同类 1-吃父节点 2-被父节点吃
  10.    
  11. public:
  12.     // 构造函数:初始化并查集
  13.     UnionFind(int n) : parent(n+1), rank(n+1, 0), relation(n+1, 0) {
  14.         for(int i = 0; i <= n; ++i) {
  15.             parent[i] = i; // 初始时每个元素的父节点是自己
  16.         }
  17.     }
  18.    
  19.     // 查找根节点(带路径压缩和关系维护)
  20.     int find(int x) {
  21.         if(parent[x] != x) {
  22.             int orig_parent = parent[x];
  23.             parent[x] = find(parent[x]); // 路径压缩
  24.             relation[x] = (relation[x] + relation[orig_parent]) % 3; // 更新关系
  25.         }
  26.         return parent[x];
  27.     }
  28.    
  29.     // 合并**(带关系维护)
  30.     bool unite(int x, int y, int type) {
  31.         int rootX = find(x);
  32.         int rootY = find(y);
  33.         
  34.         if(rootX == rootY) { // 已在同一**
  35.             // 检查关系是否矛盾
  36.             if((relation[x] - relation[y] + 3) % 3 != type)
  37.                 return false;
  38.             return true;
  39.         }
  40.         
  41.         // 按秩合并(小树合并到大树)
  42.         if(rank[rootX] > rank[rootY]) {
  43.             parent[rootY] = rootX;
  44.             relation[rootY] = (relation[x] - relation[y] - type + 6) % 3;
  45.         } else {
  46.             parent[rootX] = rootY;
  47.             relation[rootX] = (relation[y] - relation[x] + type + 3) % 3;
  48.             if(rank[rootX] == rank[rootY]) {
  49.                 rank[rootY]++; // 秩相同时合并后秩+1
  50.             }
  51.         }
  52.         return true;
  53.     }
  54. };

  55. int main() {
  56.     ios::sync_with_stdio(false);
  57.     cin.tie(nullptr);
  58.    
  59.     int N, K;
  60.     cin >> N >> K;
  61.    
  62.     UnionFind uf(N);
  63.     int falseCount = 0;
  64.    
  65.     for(int i = 0; i < K; ++i) {
  66.         int type, x, y;
  67.         cin >> type >> x >> y;
  68.         
  69.         // 输入合法性检查
  70.         if(x > N || y > N || (type == 2 && x == y)) {
  71.             falseCount++;
  72.             continue;
  73.         }
  74.         
  75.         // 关系类型转换:1->0(同类), 2->1(捕食)
  76.         int rel = type - 1;
  77.         
  78.         if(!uf.unite(x, y, rel)) {
  79.             falseCount++;
  80.         }
  81.     }
  82.    
  83.     cout << falseCount << endl;
  84.     return 0;
  85. }
复制代码

三、关键算法要点
  • 关系维护技巧:通过模3运算维护三种关系
  • 路径压缩优化:在find操作中同时更新关系
  • 按秩合并策略:保持树结构的平衡性
  • 关系验证机制:合并时检查关系是否矛盾

四、复杂度分析
  • 时间复杂度:O(Kα(N)),其中α是反阿克曼函数
  • 空间复杂度:O(N)

五、常见错误提醒
  • 忘记处理自相矛盾的情况(X吃X)
  • 关系更新公式错误
  • 输入范围检查遗漏
  • 模运算处理不当

六、学习价值
通过本题可以掌握:
  • 带权并查集的设计思想
  • 关系传递性的数学建模
  • 路径压缩与按秩合并的协同工作
  • 复杂关系的维护技巧

来源:带权并查集实战:2001年NOI食物链问题详解

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表