Hard
题目描述
给定一个正整数 n。
有一个无向图,包含 n 个节点,标号从 0 到 n - 1。初始时,图中没有边。
还给定一个二维整数数组 edges,其中 edges[i] = [ui, vi, wi] 表示节点 ui 和 vi 之间的一条权重为 wi 的边。权重 wi 只能是 0 或 1。
按照 edges 中给定的顺序处理边。对于每条边,只有在将其添加到图中后,生成图中每个环的边权重之和都是偶数时,才将该边添加到图中。
返回成功添加到图中的边数。
示例 1:
输入:n = 3, edges = [[0,1,1],[1,2,1],[0,2,1]]
输出:2
解释:
- [0, 1, 1]:在顶点 0 和顶点 1 之间添加权重为 1 的边。
- [1, 2, 1]:在顶点 1 和顶点 2 之间添加权重为 1 的边。
- [0, 2, 1]:不添加顶点 0 和顶点 2 之间的边,因为环 0-1-2-0 的总边权重为 1+1+1=3,是奇数。
示例 2:
输入:n = 3, edges = [[0,1,1],[1,2,1],[0,2,0]]
输出:3
解释:
- [0, 1, 1]:在顶点 0 和顶点 1 之间添加权重为 1 的边。
- [1, 2, 1]:在顶点 1 和顶点 2 之间添加权重为 1 的边。
- [0, 2, 0]:添加顶点 0 和顶点 2 之间权重为 0 的边。
注意环 0-1-2-0 的总边权重为 1+1+0=2,是偶数。
约束条件:
- 3 <= n <= 5 * 10^4
- 1 <= edges.length <= 5 * 10^4
- edges[i] = [ui, vi, wi]
- 0 <= ui < vi < n
- 所有边都不相同
- wi = 0 或 wi = 1
解题思路
这道题的关键在于理解环权重和的奇偶性约束。我们可以将问题转化为二分图着色问题。
核心思想:
- 给每个节点分配一个位值(0或1)
- 权重为0的边连接相同位值的节点
- 权重为1的边连接不同位值的节点
- 如果满足这个约束,任何环的权重和都是偶数
具体分析:
- 对于权重为0的边(u,v):要求bit[u] = bit[v]
- 对于权重为1的边(u,v):要求bit[u] ≠ bit[v]
使用并查集来维护连通性和相对奇偶性:
- 每个节点维护相对于其根节点的奇偶性关系
- 当添加新边时,检查是否与已有约束冲突
- 如果两个节点已连通,检查它们的相对关系是否与边权重一致
- 如果不连通,合并两个连通分量
算法流程:
- 初始化并查集,每个节点的相对奇偶性为0
- 对每条边,找到两端点的根节点
- 如果根节点相同(已连通),检查相对奇偶性是否与边权重匹配
- 如果根节点不同(未连通),根据边权重合并连通分量
- 只有不产生冲突的边才被添加
代码实现
class Solution {
public:
vector<int> parent, parity;
int find(int x) {
if (parent[x] != x) {
int old_parent = parent[x];
parent[x] = find(old_parent);
parity[x] ^= parity[old_parent];
}
return parent[x];
}
int numberOfEdgesAdded(int n, vector<vector<int>>& edges) {
parent.resize(n);
parity.resize(n);
for (int i = 0; i < n; i++) {
parent[i] = i;
parity[i] = 0;
}
int count = 0;
for (auto& edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
int root_u = find(u), root_v = find(v);
if (root_u == root_v) {
// Already connected, check if parity matches
if ((parity[u] ^ parity[v]) == w) {
count++;
}
} else {
// Not connected, merge components
parent[root_v] = root_u;
parity[root_v] = parity[u] ^ parity[v] ^ w;
count++;
}
}
return count;
}
};
class Solution:
def numberOfEdgesAdded(self, n: int, edges: List[List[int]]) -> int:
parent = list(range(n))
parity = [0] * n
def find(x):
if parent[x] != x:
old_parent = parent[x]
parent[x] = find(old_parent)
parity[x] ^= parity[old_parent]
return parent[x]
count = 0
for u, v, w in edges:
root_u = find(u)
root_v = find(v)
if root_u == root_v:
# Already connected, check if parity matches
if (parity[u] ^ parity[v]) == w:
count += 1
else:
# Not connected, merge components
parent[root_v] = root_u
parity[root_v] = parity[u] ^ parity[v] ^ w
count += 1
return count
public class Solution {
private int[] parent, parity;
private int Find(int x) {
if (parent[x] != x) {
int oldParent = parent[x];
parent[x] = Find(oldParent);
parity[x] ^= parity[oldParent];
}
return parent[x];
}
public int NumberOfEdgesAdded(int n, int[][] edges) {
parent = new int[n];
parity = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
parity[i] = 0;
}
int count = 0;
foreach (var edge in edges) {
int u = edge[0], v = edge[1], w = edge[2];
int rootU = Find(u), rootV = Find(v);
if (rootU == rootV) {
// Already connected, check if parity matches
if ((parity[u] ^ parity[v]) == w) {
count++;
}
} else {
// Not connected, merge components
parent[rootV] = rootU;
parity[rootV] = parity[u] ^ parity[v] ^ w;
count++;
}
}
return count;
}
}
var numberOfEdgesAdded = function(n, edges) {
const parent = Array(n).fill(0).map((_, i) => i);
const parity = Array(n).fill(0);
function find(x) {
if (parent[x] !== x) {
const root = find(parent[x]);
parity[x] ^= parity[parent[x]];
parent[x] = root;
}
return parent[x];
}
let count = 0;
for (const [u, v, w] of edges) {
const rootU = find(u);
const rootV = find(v);
if (rootU !== rootV) {
parent[rootV] = rootU;
parity[rootV] = parity[u] ^ parity[v] ^ w;
count++;
} else {
const cycleWeight = parity[u] ^ parity[v] ^ w;
if (cycleWeight === 0) {
count++;
}
}
}
return count;
};
复杂度分析
| 复杂度类型 | 复杂度值 | 说明 |
|---|---|---|
| 时间复杂度 | O(E·α(N)) | E为边数,α为阿克曼函数的反函数,几乎为常数 |
| 空间复杂度 | O(N) | 存储并查集的parent和parity数组 |