题解:AcWing 237 程序自动分析

【题目来源】

AcWing:237. 程序自动分析 - AcWing题库

【题目描述】

在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。

考虑一个约束满足问题的简化版本:假设 \(x_1,x_2,x_3,…\) 代表程序中出现的变量,给定 \(n\) 个形如 \(x_i=x_j\) 或 \(x_i≠x_j\) 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。

例如,一个问题中的约束条件为:\(x_1=x_2\)\(x_2=x_3\)\(x_3=x_4\)\(x_1≠x_4\),这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。

现在给出一些约束满足问题,请分别对它们进行判定。

【输入】

输入文件的第 \(1\) 行包含 \(1\) 个正整数 \(t\),表示需要判定的问题个数,注意这些问题之间是相互独立的。

对于每个问题,包含若干行:

第 \(1\) 行包含 \(1\) 个正整数 \(n\),表示该问题中需要被满足的约束条件个数。

接下来 \(n\) 行,每行包括 \(3\) 个整数 \(i,j,e\),描述 \(1\) 个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 \(e=1\),则该约束条件为 \(x_i=x_j\);若 \(e=0\),则该约束条件为 \(x_i≠x_j\)

【输出】

输出文件包括 \(t\) 行。

输出文件的第 \(k\) 行输出一个字符串 YES 或者 NOYES 表示输入中的第 \(k\) 个问题判定为可以被满足,NO 表示不可被满足。

【输入样例】

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

【输出样例】

NO
YES

【核心思想】

  1. 问题分析:给定 \(t\) 组测试数据,每组包含 \(n\) 个变量之间的相等(\(x_i = x_j\))或不等(\(x_i \neq x_j\))约束条件,判断是否存在一种赋值方案使得所有约束同时满足。这是一个并查集问题,关键在于利用相等关系的传递性:若 \(x_i = x_j\)\(x_j = x_k\),则 \(x_i = x_k\)

  2. 算法选择

    • 并查集(Disjoint Set Union, DSU):维护变量的等价类,支持 \(O(\alpha(n))\) 的合并与查询
    • 离散化(Hash/Map):变量编号可能很大(达到 \(10^9\)),需要映射为连续的编号
    • 两遍扫描策略:先合并所有相等关系,再检查不等关系是否冲突
  3. 关键步骤

    • 离散化:使用 unordered_map 将原始变量值 \(x, y\) 映射为 \(1 \sim n\) 的连续编号
    • 初始化并查集p[i] = i,每个变量初始独立
    • 第一遍:处理相等约束\(e = 1\)):
      • 对于每个相等约束 \((x_i, x_j)\)
        • pa = find(x_i), pb = find(x_j)
        • p[pa] = pb:将两个等价类合并
    • 第二遍:检查不等约束\(e = 0\)):
      • 对于每个不等约束 \((x_i, x_j)\)
        • pa = find(x_i), pb = find(x_j)
        • pa == pb,说明 \(x_i\)\(x_j\) 已在同一等价类,与不等约束矛盾,输出 NO
    • 结果:若所有不等约束均不矛盾,输出 YES
  4. 时间/空间复杂度

    • 时间复杂度:\(O(m \cdot \alpha(n))\),其中 \(m\) 为约束条数,\(\alpha\) 为阿克曼函数反函数,近似常数。离散化 \(O(m)\),并查集操作 \(O(m \cdot \alpha(n))\)
    • 空间复杂度:\(O(n)\),并查集父节点数组和离散化映射表
  5. 并查集的核心思想

    • 等价类维护:将相等的变量归入同一集合,利用路径压缩优化查询效率
    • 传递闭包:相等关系具有传递性,并查集天然支持这种闭包计算
    • 冲突检测策略:必须先处理所有相等关系(建立等价类),再检查不等关系。若顺序颠倒,可能导致错误判断
    • 离散化必要性:当变量范围极大而实际出现次数较少时,通过哈希映射压缩空间
    • 适用于变量分组、连通性判断、等价关系推理类问题

【算法标签】

并查集

【代码详解】

#include <bits/stdc++.h>
using namespace std;
const int N = 200005;       // 最大节点数(离散化后最多 n 个节点)
int n, m, T;                // n: 离散化后的节点计数, m: 约束条件个数, T: 测试用例数
int p[N];                   // 并查集父节点数组
unordered_map<int, int> S;  // 离散化映射:原始变量值 -> 连续编号

struct Query
{
    int x, y, e;            // x,y: 离散化后的变量编号, e: 约束类型(1相等,0不等)
} query[N];                 // 存储所有约束条件

// 离散化:将原始变量值映射为连续编号(从1开始)
int get(int x)
{
    if (S.count(x) == 0) S[x] = ++n;  // 若该值未出现过,分配新编号(n 从0递增)
    return S[x];                       // 返回该变量对应的编号
}

// 并查集查找(路径压缩)
int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);  // 递归查找根节点并压缩路径
    return p[x];                        // 返回根节点
}

int main()
{
    cin >> T;                    // 读入测试用例数
    while (T--)                  // 遍历每个测试用例
    {
        n = 0;                   // 重置节点计数器(用于离散化)
        S.clear();               // 清空离散化映射
        cin >> m;                // 读入约束条件个数
        for (int i = 1; i <= m; i++)
        {
            int x, y, e;
            cin >> x >> y >> e;              // 读入原始变量值和约束类型
            query[i] = {get(x), get(y), e};   // 离散化后存入查询数组
        }
        for (int i = 1; i <= n; i++) p[i] = i;  // 初始化并查集:每个节点自成一个集合

        // 第一遍:处理所有 "相等" 约束(e=1),合并对应集合
        for (int i = 1; i <= m; i++)
        {
            if (query[i].e == 1)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                p[pa] = pb;  // 将 x 所在集合的根指向 y 所在集合的根
            }
        }

        // 第二遍:检查所有 "不等" 约束(e=0)是否产生矛盾
        bool has_conflict = false;
        for (int i = 1; i <= m; i++)
        {
            if (query[i].e == 0)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                if (pa == pb)           // 若原本应不等的两个变量已在同一集合
                {
                    has_conflict = true;  // 发现矛盾,约束不可同时满足
                    break;
                }
            }
        }
        if (has_conflict) cout << "NO" << endl;   // 存在矛盾,不可满足
        else cout << "YES" << endl;               // 无矛盾,可以满足
    }
    return 0;
}

【运行结果】

2
2
1 2 1
1 2 0
NO
2
1 2 1
2 1 1
YES
posted @ 2026-06-23 11:23  团爸讲算法  阅读(9)  评论(0)    收藏  举报