网易互娱笔试题:
假设教授A欣赏B,B欣赏C,则认为A也欣赏C。
输入 第一行两个整数 n m,分别表示教授人数,欣赏关系数量
余下m行,每行两个整数 a b,表示教授a欣赏b。
输入样例:
5 6
1 3
2 1
3 2
3 5
4 5
5 4
输出:
4
考试提交的版本,直接迭代求解邻接矩阵(超时
def solve(N:int, Edges:[[int]]):
Map = [[False]*N for _ in range(N)]
for u, v in Edges:
Map[u-1][v-1] = True
flag = True
while flag:
flag = False
for i in range(N):
for j in range(N):
if Map[i][j]:
for k in range(N):
if k != j and k != i:
if Map[j][k] and not Map[i][k]:
flag = True
Map[i][k] = True
count = 0
for i in range(N-1):
for j in range(i+1, N):
if Map[i][j] and Map[j][i]:
count += 1
return count
考试实验的第二个版本,当然也超时了
class Node:
def __init__(self, value):
self.value = value
self.next = set()
self.last = set()
def append(self, i:int, node_dict:{}):
if i == self.value or i in self.next:
return
else:
self.next.add(i)
for j in node_dict[i].next:
self.append(j,node_dict)
for l in self.last:
node_dict[l].append(i, node_dict)
def appreciate(self, i):
return i in self.next
def solve2(N:int, Edges:[[int]]):
if len(Edges) <=1:
return 0
Node_dict = {}
for U, V in Edges:
u = U - 1
v = V - 1
if u not in Node_dict.keys():
Node_dict[u] = Node(u)
if v not in Node_dict.keys():
Node_dict[v] = Node(v)
Node_dict[u].append(v, Node_dict)
Node_dict[v].last.add(u)
count = 0
for i in range(N-1):
# print(i, Node_dict[i].next, Node_dict[i].last)
for j in range(i+1, N):
if Node_dict[i].appreciate(j) and Node_dict[j].appreciate(i):
count += 1
# print(i,j)
return count
我花了爆久才能想得通这个为什么是正确的。因为第一次是后序dfs,所以编号最大的一定是某个连通分量的根R,第二次求反图,如果能找到任何一个反向边,这个边一定连向某个编号比它小的点,也就意味着是第一次dfs时R的子树,反向边本身意味着R是它的子树,那么就这样构成了回路。所以通过求反图,就可以遍历到R所在的整个强连通分量
会不会存在一种情况,第一次后序dfs从不同根节点开始便利了几次,这样在前面dfs到的节点编号(假设是u)就会比后面小,但前面的节点(u)就可能并不是后面更大编号点(假设是v)的子树,而求反图恰恰发现v连向u,这样就不符合上述逻辑?
答:不会,假设反图中更大编号点v连向更小的点u,则在第一次dfs,u是在v之前遍历到的,且u具有指向v的边,那么在编号时,v点的编号一定比u小,与假设矛盾
class Node2:
def __init__(self, value):
self.value = value
self.next = set()
self.last = set()
self.number = -1
self.mark = False
def to(self, s):
self.next.update(s)
def _from(self, s):
self.last.update(s)
def last_order_dfs(self, node_dict, number, have_past):
if self.value in have_past:
return number
else:
have_past.append(self.value)
for next_node in self.next:
number = node_dict[next_node].last_order_dfs(node_dict, number, have_past)
self.number = number
return number + 1
def second_dfs(self, node_dict):
count = 0
if self.mark:
return 0
else:
self.mark = True
count += 1
for last_node in self.last:
count += node_dict[last_node].second_dfs(node_dict)
return count
def solve3(N:int, Edges:[[int]]):
Node_dict = {i+1:Node2(i+1) for i in range(N)}
for u, v in Edges:
Node_dict[u].next.add(v)
Node_dict[v].last.add(u)
number = 1
second_search = {}
have_past = []
for index in range(1, N+1):
number = Node_dict[index].last_order_dfs(Node_dict, number, have_past)
second_search[Node_dict[index].number] = index
branch_size = []
for i in range(N, 0, -1):
index = second_search[i]
branch_size.append(Node_dict[index].second_dfs(Node_dict))
ret = 0
for size in branch_size:
ret += int(comb(size,2))
return ret
我看完缩点原理就以为自己会了,如果构成回路,就把回路上的点搜索为一个点,但是我根据这个原理就是实现的非常糟糕。
最后还是参考了https://byvoid.com/zhs/blog/scc-tarjan/
但其实还是不会灵活运用QAQ
class Node3:
def __init__(self, value):
self.value = value
self.next = set()
self.have_searched = False
self.number = -1
self.contain = 0
def dfs(self, node_dict, count, have_past):
if self.number != -1:
return self.number, count, have_past
count += 1
self.number = count
children = [count]
have_past.append(count)
for next_node in self.next:
child, count, have_past = node_dict[next_node].dfs(node_dict, count, have_past)
children.append(child)
low = min(children)
if low == self.number:
count = 1
while have_past[-count] != self.number:
count += 1
self.contain = count
have_past = have_past[:-count]
return low, count, have_past
def solve4(N:int, Edges:[[int]]):
Node_dict = {i+1:Node3(i+1) for i in range(N)}
for u, v in Edges:
Node_dict[u].next.add(v)
ret = 0
for index in range(1,N+1):
Node_dict[index].dfs(Node_dict, 1, [])
ret += int(comb(Node_dict[index].contain, 2))
return ret