一、题目:
危险系数
抗日战争时期,冀中平原的地道战曾发挥重要作用。
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数DF(x,y):
对于两个站点x和y (x != y), 如果能找到一个站点z,当z被敌人破坏后,x和y不连通,那么我们称z为关于x,y的关键点。相应的,对于任意一对站点x和y,危险系数DF(x,y)就表示为这两点之间的关键点个数。
本题的任务是:已知网络结构,求两站点之间的危险系数。
【输入形式】
输入数据第一行包含2个整数n(2 <= n <= 1000), m(0 <= m <= 2000),分别代表站点数,通道数;
接下来m行,每行两个整数 u,v (1 <= u, v <= n; u != v)代表一条通道;
最后1行,两个数u,v,代表询问两点之间的危险系数DF(u, v)。
【输出形式】
一个整数,如果询问的两点不连通则输出-1.
【样例输入】
7 6
1 3
2 3
3 4
3 5
4 5
5 6
1 6
【样例输出】
2
二、TarJan算法介绍:
为了这道题到处学的Tarjan算法,整理在:直观地简单理解Tarjan算法(寻找有向图中的强连通图)
三、思路和代码
将问题转化为有向强连通图中去掉各个点后,u、v是否仍然强连通。具体实现中有详细的备注。
代码:
def toLinkedDirectedGragh(ls):
# 将输入转化成有向图的链表存储形式
linkedTable = [list() for i in range(n+1)]
for x in ls:
linkedTable[x[0]].append(x[1])
linkedTable[x[1]].append(x[0])
return linkedTable
def Tarjan(x, f):
# res存储出栈的结果,lkt为单向图链表, index是Tarjan搜索到该结点时的顺序用于初始化dfs和low数组
global lkt,stack, res, u, index,Low,Dfs
if used[x]==0:
Low[x]=index
Dfs[x]=index
index += 1
stack.append(x)
used[x]=1
for i in lkt[x]:
Tarjan(i, x)
if Low[i]<Low[x]:
Low[x] = Low[i]
if Dfs[x]==Low[x]:
#将自己及之后的结点全部退栈作为一个强连通图存入结果
res.append(stack[stack.index(x):])
stack = stack[:stack.index(x)]
else:
for s in stack:
# 如果x站点在栈中
if s==x:
#若根更小则更新
if Low[s] < Low[f]:
Low[f] = Low[s]
break
#while True:
# 获取输入
n, m = map(int, input().split())
ls = list()
for i in range(m):
ls.append(list(map(int, input().split())))
u, v = map(int, input().split())
# 将无向图转化为单向图,并用链表存储
lkt = toLinkedDirectedGragh(ls)
Low=[0 for i in range(n+1)]
Dfs=[0 for i in range(n+1)]
used = [0 for i in range(n+1)]
stack = list()
res = list()
index = 1
Low[1]=index
Dfs[1]=index
Tarjan(u, 0)
#
dangerrate=0
"""找到含有v,u的强连通图并存入res,但增加一些判断可以避免遍历所有的元素,这里有更简洁的写法,如:
for re in res:
if v in re and u in re:
find =True"""
uin = False;vin = False
isfind = False
for re in res:
for x in re:
if v == x:
vin=True
if u ==x:
uin=True
if vin and uin:
isfind=True
res = re
break
if isfind:
break
# 若u、v本来就连通则将该强连通图res中的其他节点去掉并查看是否仍然连通
if isfind:
rest = res
for x in rest:
if x!=u and x!= v:
lst = list()
# lst为去掉x后的新的无向边集
for t in ls:
if x not in t:
lst.append(t)
lkt = toLinkedDirectedGragh(lst)
# 初始化会用到的全局变量
Low=[0 for i in range(n+1)]
Dfs=[0 for i in range(n+1)]
used = [0 for i in range(n+1)]
stack = list()
res = list()
# 入栈顺序index
index = 1
Tarjan(u, 0)
dangerrate += 1
for re in res:
if v in re and u in re:
dangerrate -= 1
print(dangerrate)
else:
print(-1)
四、反思总结
在写这道题时原本用的是很容易想到的方法(俗称直觉),但总有一个样例超时,于是寻找效率更高的算法才找到了Tarjan算法。一趟学习下来虽然花了不少时间但也掌握了一个新的算法。
在将算法转换成代码时仍然会出现许多问题,导致在debug上花费过多时间。这些问题最终提炼出以下一点写代码的经验:
1、在编写函数时,对函数功能先要有更加具体的概括,特别是对于递归函数。
例如:对于Tarjan函数,输入的x和f分别代表下一个要处理的结点和其父节点。明确这一点后就知道所有处理x时要做的事应该在函数开头做,而不是在内部递归调用之前想着去处理。
2、养成采取合适数据结构的习惯。当一个问题适合采用一种数据结构时,可以花一点时间先去将数据转换为这种结构,而不是能嫌麻烦。一来可以锻炼自己对常用数据结构的理解,二来经典的数据结构会让后面的程序编写更加简洁,运行效率常常更高。
例如:一开始对于low和dfs我是创建了一个结点node分别有num、dfs、low属性,然后以node为加入栈的对象。但这导致之后每次比较low属性时需要根据num遍历栈找对应的对象。对象访问属性等的时间开销又大,有时可以用元组的方式组织数据。而按照原方法的Low和Dfs数组就很好地解决了这个问题。
还有本题的链式存储有向图等。
3、测试代码时多创造一些用例。用例不用很复杂。更多的用例常常可以在提交前暴露出代码的一些问题。
4、debug的时候不要盲目,最好从全局到局部,按照模块来。在每个模块结尾设置断点,根据模块功能查看相应变量的变化情况,更容易定位到bug的位置。
我的发言到此结束,谢谢大家。