'''
并查集实现, 拓展版本,可以用回调把数据转成内部key, 然后内部用线性表来实现
'''
class MergeSetExt:
def __init__(self, max_key_val = 0, trans_key_func=None):
if trans_key_func is not None and max_key_val > 0:
# 如果能够提供转换key的回调,并查集内部用线性表存储
self.trans_key_callback = trans_key_func
self.m = [-1 for _ in range(max_key_val+1)]
self.__root2cluster_size = [0 for _ in range(max_key_val+1)]
else:
# 如果不能提供转换key的回调,并查集内部用hash表存储
self.trans_key_callback = None
self.m = {}
self.__root2cluster_size = {}
self.__root_cnt = 0
def getRoot(self, node):
buf = []
root = self.trans_key_callback(node) if self.trans_key_callback else node
while self.m[root] != root:
buf.append(root)
root = self.m[root]
for key in buf:
self.m[key] = root
return root
def merge(self, a, b):
orig_a, orig_b = a, b
if self.trans_key_callback:
a, b = self.trans_key_callback(a), self.trans_key_callback(b)
for node in [a, b]:
if (self.trans_key_callback is None and node not in self.m) or (self.trans_key_callback is not None and self.m[node] == -1):
self.m[node] = node
self.__root2cluster_size[node] = 1
self.__root_cnt += 1
root1 = self.getRoot(orig_a)
root2 = self.getRoot(orig_b)
if root1 != root2:
self.m[root1] = root2
self.__root2cluster_size[root2] += self.__root2cluster_size[root1]
if self.trans_key_callback:
self.__root2cluster_size[root1] = 0
else:
self.__root2cluster_size.pop(root1)
self.__root_cnt -= 1
# 根据根节点的数值获取其所在的簇的大小
def getClusterSize(self, root):
return self.__root2cluster_size[root]
def isInSameSet(self, a, b):
if a == b:
return True
orig_a, orig_b = a, b
if self.trans_key_callback:
a, b = self.trans_key_callback(a), self.trans_key_callback(b)
for node in [a, b]:
if self.m[node] == -1:
return False
else:
for node in [orig_a, orig_b]:
if node not in self.m:
return False
return self.getRoot(orig_a) == self.getRoot(orig_b)
def getRootNum(self):
return self.__root_cnt
def getClusters(self):
rec = {}
if self.trans_key_callback is None:
for node in self.m:
root = self.getRoot(node)
if root not in rec:
rec[root] = []
rec[root].append(node)
else:
for node in range(len(self.m)):
if self.m[node] == -1:
continue
root = self.getRoot(node)
if root not in rec:
rec[root] = []
rec[root].append(node)
return [nodes for nodes in rec.values()]
m, n = map(int, input().split())
# 克鲁斯卡尔算法求解最小生成树输入为边列表,每条边表示为(起点,终点,权重)
# 返回最小生成树权重总和,以及最小生成树边列表, 生成不了最小生成树返回None
def getMinSpanTreeKruscal(init_edges):
merge_set = MergeSetExt(m*n+1, lambda x : (x[0]-1)*n + x[1])
sum = 0
cnt = 0
for a1, b1, a2, b2 in init_edges:
if not merge_set.isInSameSet((a1, b1), (a2, b2)):
merge_set.merge((a1, b1), (a2, b2))
cnt += 1
for i in range(1, m+1):
for j in range(1, n+1):
if i+1 <= m:
a1, b1, a2, b2, w = i, j, i+1, j, 1
if merge_set.isInSameSet((a1, b1), (a2, b2)):
continue
merge_set.merge((a1, b1), (a2, b2))
sum += w
cnt += 1
if cnt == m*n - 1:
return sum
for i in range(1, m+1):
for j in range(1, n+1):
if j+1 <= n:
a1, b1, a2, b2, w = i, j, i, j+1, 2
if merge_set.isInSameSet((a1, b1), (a2, b2)):
continue
merge_set.merge((a1, b1), (a2, b2))
sum += w
cnt += 1
if cnt == m*n - 1:
return sum
return sum
init_edges = []
while True:
try:
i1, j1, i2, j2 = map(int, input().split())
init_edges.append((i1, j1, i2, j2))
except:
break
print(getMinSpanTreeKruscal(init_edges))