蓝桥杯-危险系数(python实现,TarJan算法)

一、题目:

危险系数
抗日战争时期,冀中平原的地道战曾发挥重要作用。
地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。
我们来定义一个危险系数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的位置。

我的发言到此结束,谢谢大家。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值