2. 题意简述 (Problem Summary)
给定 t 个相互独立的约束满足问题。每个问题包含 n 条形如 (i,j,e) 的约束:e=1 表示 xi=xj,e=0 表示 xi=xj。变量编号 i,j 的范围为 1≤i,j≤109。对每个问题,判断是否存在一种变量赋值,使得所有约束同时满足。
输入:t(1≤t≤10),随后每组数据 n(1≤n≤105)及 n 条约束。
输出:对每组数据输出一行,YES 表示可满足,NO 表示不可满足。
3. 朴素解法 (Brute-Force)
最直接的想法是枚举所有变量的赋值。但变量编号可达 109,且约束数 n 最大为 105,变量个数最多可达 2×105,完全枚举不可行。
另一种思路是建立约束图,用 DFS 染色检测相等关系的连通性,再逐一检查不等关系。但变量编号过大,无法直接开数组建图,且需要处理传递性,实现起来并不比并查集更简单。
4. 核心解法 (Main Solution)
特殊性质
相等关系具有传递性:若 xi=xj 且 xj=xk,则必有 xi=xk。因此,所有相等的变量在逻辑上属于同一个等价类。不等关系则要求两个变量必须落在不同的等价类中。
关键突破
利用传递性,我们可以先用并查集(Union-Find)把所有相等约束合并,再逐一检查不等约束。若某条不等约束的两个变量已被合并到同一集合,则矛盾出现,问题不可满足。
变量编号 i,j 最大为 109,无法直接作为并查集数组下标,因此需要离散化:收集每组数据中出现过的所有编号,排序后映射为 0∼m−1(或 1∼m),其中 m≤2n。
推导过程
算法流程如下:
- 收集与离散化:读入所有约束,将出现的 i,j 存入数组,排序去重后建立映射 id[x]。
- 合并相等关系:遍历所有 e=1 的约束,将 id[i] 与 id[j] 合并。
- 检查不等关系:遍历所有 e=0 的约束,若 find(id[i])=find(id[j]),则矛盾,输出
NO。
- 若所有不等约束均不矛盾,输出
YES。
下面我们证明这个流程的正确性。
5. 正确性证明 (Proof of Correctness)
引理 1(并查集合并的正确性):若 xi=xj,则经过并查集合并后,xi 与 xj 属于同一集合。并查集的合并操作满足等价关系的自反性、对称性和传递性,因此所有通过相等关系传递可达的变量最终都会被合并到同一集合。
引理 2(检查的充分性):若存在 e=0 的约束 xi=xj,但 find(id[i])=find(id[j]),则问题无解。因为并查集合并了所有相等关系,若 xi 与 xj 在同一集合,说明 xi=xj 可由已有相等约束推导得出,与 xi=xj 矛盾。
定理(算法正确):上述算法输出 YES 当且仅当约束可满足。
证明:若算法输出 NO,则由引理 2 可知存在矛盾,故不可满足。若算法输出 YES,则所有不等约束的两个变量均在不同集合。此时为每个集合分配一个唯一值(如集合的根节点编号),相等约束因同集合而自动满足,不等约束因不同集合而自动满足。因此存在可行赋值,约束可满足。综上所述,算法正确。
6. 复杂度分析 (Complexity)
- 时间复杂度:O(nlogn)。每组数据中,离散化排序 O(nlogn),并查集操作 O(nα(m)),其中 α 为反阿克曼函数,可视为常数。n≤105 时,nlogn≈1.7×106,2s 内可轻松通过。
- 空间复杂度:O(n)。离散化数组与并查集数组各 O(n),总空间约几 MB,远低于 512 MB 限制。所以数组略微开大一点也没关系。
7. 实现细节与避坑指南 (Implementation Details)
| 坑点 |
说明 |
| 离散化映射 |
变量编号最大 109,必须离散化。收集时去重,映射时用 lower_bound 或哈希表。m≤2n。 |
| 多组数据清空 |
t≤10,每组数据独立。离散化数组、并查集父数组、约束存储数组均需在每组数据开始时重新初始化。 |
| 合并顺序 |
必须先合并所有相等关系,再检查不等关系。若先检查不等关系,会漏掉经由相等传递才产生的矛盾。 |
| 数组下标 |
离散化后下标从 0 或 1 开始均可,但并查集数组大小要相应调整开大一点(约两倍),避免越界。 |
8. 参考代码 (Reference Code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| #include <iostream> #include <algorithm> using namespace std;
const int MAXN = 1e6 + 5;
int t, n; int fa[MAXN];
int i1[MAXN], j1[MAXN], a1; int i0[MAXN], j0[MAXN], a0; int a[MAXN], b[MAXN], m;
int get(int x) { return fa[x] == x ? x : fa[x] = get(fa[x]); }
int query(int x) { return lower_bound(b, b + m, x) - b; }
int main() { ios::sync_with_stdio(0); cin.tie(0);
cin >> t; while (t--) { cin >> n; m = a1 = a0 = 0; for (int i = 1; i <= 2 * n; i++) fa[i] = i; for (int i = 1; i <= n; i++) { int x, y, z; cin >> x >> y >> z; if (z == 0) { a0++; i0[a0] = x; j0[a0] = y; } if (z == 1) { a1++; i1[a1] = x; j1[a1] = y; } a[2 * i - 1] = x; a[2 * i] = y; } sort(a + 1, a + 1 + 2 * n); for (int i = 1; i <= 2 * n; i++) { if (i == 1 || a[i] != a[i - 1]) b[++m] = a[i]; }
for (int i = 1; i <= a1; i++) fa[get(query(i1[i]))] = get(query(j1[i]));
bool flag = 1; for (int i = 1; i <= a0 && flag; i++) flag &= (get(query(i0[i])) != get(query(j0[i])));
cout << (flag ? "YES" : "NO") << '\n'; } return 0; }
|
9. 补充说明 (Additional Notes)
- 本题是约束满足问题(Constraint Satisfaction Problem, CSP)的最简形式,只含相等与不等两类二元约束。更一般的 CSP 允许任意定义域与任意关系约束,通常是 NP-Complete 的;但本题由于约束结构极简单,仅靠并查集的合并与查找即可在多项式时间内判定。
- 若把"相等"视为无向边、"不等"视为不能连通的判定,这题本质上是在问:由相等关系生成的等价类图,是否与不等关系冲突。这个视角也可以扩展到带权约束(如 xi−xj≥c)或多元约束,那时需要改用带权并查集、差分约束或 SAT 求解器。
- 若约束中还包含 xi<xj 等偏序关系,则需要更复杂的数据结构(如带权并查集),但本题无需考虑。
其他版本
本题的写法有很多变体。一种常见的写法是用结构体数组存储所有约束,读入时统一离散化,再分两遍处理。时间复杂度和空间复杂度与上述版本相同。