【蓝桥杯】危险系数Python满分解法
前言
小白一枚,花了一天的时间整了蓝桥杯的危险系数方法,在此记录一下解法~~
一、方法一
首先,我一开始使用的方法是进行树的深度优先搜索,将所有的从u节点到v节点的所有路径集合,然后看看那些路径在路径集合中都出现。记录一下这些反复出现的路径所对应的节点。最后将节点进行输出
当然,这种算法时间复杂度为n!,对于Python代码来说超时了。。。
class Solution:
#该题目用来求有n个节点,m条通道的,以edges构造的地道树中,节点u和节点v之间的危险系数
def dangerCoefficient(self,n,m,edges,u,v):
def dsf(val,v):
if val ==v:
res.append(path.copy())
for i in range(n):
if not used[i] and (edges[i][0]==val or edges[i][1]==val):
used[i]=True
path.append(edges[i])
if edges[i][0]==val:
dsf(edges[i][1],v)
else:
dsf(edges[i][0],v)
path.pop()
used[i]=False
edges.sort(key=lambda x:x[0])
used=[False for _ in range(m)]
res=list()
path=list()
ans=0
n=len(edges)
dsf(u,v)
singlar_edges=list()
nodes=set()
for temp in res[0]:
if all(temp in res_part for res_part in res):
if temp[0] not in nodes and temp[0]!=u and temp[0]!=v:
nodes.add(temp[0])
if temp[1] not in nodes and temp[1]!=u and temp[1]!=v:
nodes.add(temp[1])
ans=len(nodes)
return ans
if __name__ == "__main__":
solution = Solution
n,m=map(int,input().split())
edges=[]
for i in range(m):
temp1,temp2=map(int,input().split())
edges.append((temp1,temp2))
u,v=map(int,input().split())
## n,m=7,6
## edges=[(1,3),(2,3),(3,4),(3,5),(4,5),(5,6)]
## u,v=1,6
## n,m=5,4
## edges=[(1,3),(2,3),(1,4),(4,5)]
## u,v=2,5
result = solution.dangerCoefficient(solution,n,m,edges,u,v)
print(result)
二、方法二:满分解法
在第一种情况拿不到满分的情况下我们不得不想一下别的解法: 于是我们采用 割点 的思想。
首先,整体思路:
1.确定结构
本题目我们的边都是无向边,即(1,3)既表示从节点1可以到达节点3,又表示节点3可以到达节点1。对于这个题目,我们将一条有向边等价成两条无向边,即节点1到节点3的边和节点3到节点1的边。
2.定义两点之间的边这种类,edge
该类中包含两个属性,to_node
表示这条边的终点节点的编号。pre_edge
表示这条边的源点除了这条边本身的其余出边
可能读者不太理解这句话什么意思,没关系,看看后面的再重新读一遍这个部分就会茅塞顿开
3.根据输入构造图结构
比如我们输入的边数组为 [(1,3),(2,3),(3,4),(3,5),(4,5),(5,6)],则具体的节点和边的拓扑结构如下:
接下来我们输出一下各个边的对象看一下
好的重点来了!
我们是按照边的顺序,从0到m(不包含m)来进行每一条边的构建的。
构建编号为0的边,终点为3,其源点1没有额外的出边
构建编号为1的边,终点为1,其源点3没有额外的出边
构建编号为2的边,终点为3,其源点2没有额外的出边
构建编号为3的边,终点为2**,其源点3有一条之前的出边,编号为1**
…
…
通过如此构建,我们可以将输入的边按照某一种结构进行记录下来。我们就是通过这种结构再结合深度优先搜索不断地找寻所有的通路
4 进行割点
在进行割点前,我们先判断u和v是否联通。如果不联通,直接返回-1。如果连通,我们通过now标记当前被删除的节点。通过一个一个的遍历讨论节点,查看删除某个特定节点后原图是否联通,从而得到最终的解
5 代码
class Edge:
#to表示该条边的终点节点的编号
#pre_edge表示从该边源点出发的上一条边
def __init__(self,to_node):
self.to_node=to_node
self.pre_edge=-1
class Solution:
def dangerCoefficient(self,n,m,edges,u,v):
#u表示源点,v表示终点
def add_edge(u,v):
nonlocal cnt
edge[cnt].to=v
edge[cnt].pre_edge=pre[u]
pre[u]=cnt
cnt+=1
def dsf(s):
if s==ed:
return 1
flag[s]=True
p=pre[s]
while p!=-1:
temp=edge[p].to
p=edge[p].pre_edge
if flag[temp]==True or temp==now:
continue
if dsf(temp)==1:
flag[s]=False
return 1
flag[s]=False
return 0
flag=[False for _ in range(n)]
edge=list()
cnt=0
pre=[-1 for _ in range(n+1)]
for x,y in edges:
edge.append(Edge(y))
add_edge(x,y)
edge.append(Edge(x))
add_edge(y,x)
## for i in range(len(edge)):
## print('边的编号:',i)
## print('边的终点to:',edge[i].to_node)
## print('边的源点的另外一条出边:',edge[i].pre_edge)
## print('===================')
ed=v
now=-1
if dsf(u)==0:
return -1
else:
ans=0
for i in range(n):
if i==u or i==v:
continue
now=i
if dsf(u)==0:
ans+=1
return ans
return 0
if __name__ == "__main__":
solution = Solution
n,m=map(int,input().split())
edges=[]
for i in range(m):
temp1,temp2=map(int,input().split())
edges.append((temp1,temp2))
u,v=map(int,input().split())
## n,m=7,6
## edges=[(1,3),(2,3),(3,4),(3,5),(4,5),(5,6)]
## u,v=1,6
## n,m=5,4
## edges=[(1,3),(2,3),(1,4),(4,5)]
## u,v=2,5
result = solution.dangerCoefficient(solution,n,m,edges,u,v)
print(result)
总结
通过这次算法,基本理解了割点的思想。对于Python代码来说,一般的深度优先搜索往往会超时。而第二种方法较第一种的优点就是之前通过O(n)的时间,通过类来将该图的结构特点进行了记录,从而避免了全部的深度优先,节省了时间
方法三
#第三种方案:
#思路是先通过遍历来保留树的结构
#然后割点
from time import time
class Solution:
def dangerCoefficient(self,n,m,edges,u,v):
#n表示我们讨论到第n个点
def dsf(n):
if n==v:
return True
## print('flag:',flag)
flag[n]=True
## for i in range(m):
for i in node_edge[n]:
## if not used[i] and now not in edges[i] and node_edge[n]>>i & 1==1 :
if not used[i] and now not in edges[i]:
used[i]=True
if edges[i][0]==n and not flag[edges[i][1]] and dsf(edges[i][1]):
flag[n]=False
used[i]=False
return True
else:
if not flag[edges[i][0]] and dsf(edges[i][0]):
flag[n]=False
used[i]=False
return True
used[i]=False
flag[n]=False
return False
node_edge=[0 for _ in range(n+1)]
cnt=0
## node_edge[i]表示的是第i个节点的相邻边
## node_edge=[[]for _ in range(n+1)]
for x,y in edges:
node_edge[x].append(cnt)
node_edge[y].append(cnt)
cnt+=1
## for x,y in edges:
## node_edge[x]|=1<<cnt
## node_edge[y]|=1<<cnt
## cnt+=1
## print(node_edge)
#used表示某一条边是否已经使用
used=[False for _ in range(m)]
#flag在此表示某一个结点是否使用了
flag=[False for _ in range(n+1)]
now=-1
#先提前进行遍历看看通不通
if not dsf(u):
return -1
ans=0
for temp in range(1,n+1):
if temp==u or temp ==v:
continue
now=temp
if not dsf(u):
ans+=1
return ans
if __name__ == "__main__":
solution = Solution
n,m=map(int,input().split())
edges=[]
for i in range(m):
temp1,temp2=map(int,input().split())
edges.append((temp1,temp2))
u,v=map(int,input().split())
## n,m=7,6
## edges=[(1,3),(2,3),(3,4),(3,5),(4,5),(5,6)]
## u,v=1,6
## n,m=5,4
## edges=[(1,3),(2,3),(1,4),(4,5)]
## u,v=2,5
## pre=time()
result = solution.dangerCoefficient(solution,n,m,edges,u,v)
## print(time()-pre)
print(result)