蓝桥杯·发现环+蓝桥侦探 Python题解(拓扑排序+并查集)

目录

题目一:发现环

题解1.1:拓扑排序

题解1.2:并查集 +DFS

题目二:蓝桥侦探

题解2.1:拓扑排序

题解2.2:并查集 


题目一:发现环

题目描述

小明的实验室有 N台电脑,编号 1 ⋯N。原本这 N台电脑之间有 N−1 条数据链接相连,恰好构成一个树形网络。在树形网络上,任意两台电脑之间有唯一的路径相连。

不过在最近一次维护网络时,管理员误操作使得某两台电脑之间增加了一条数据链接,于是网络中出现了环路。环路上的电脑由于两两之间不再是只有一条路径,使得这些电脑上的数据传输出现了 BUG。

为了恢复正常传输。小明需要找到所有在环路上的电脑,你能帮助他吗?

输入描述

输入范围:

第一行包含一个整数 N 。

以下 N 行每行两个整数 a,b,表示 a 和 b 之间有一条数据链接相连。

其中,1≤N≤105,1≤a,b≤N。

输入保证合法。

输出描述

按从小到大的顺序输出在环路上的电脑的编号,中间由一个空格分隔。

输入输出样例

输入:

5
1 2
3 1
2 4
2 5
5 3

输出:

1 2 3 5

题解1.1:拓扑排序

        拓扑排序用于来找出图中的线性序列。在排序过程中倘若到最后还有节点未处理,则说明该图中存在环。我们利用这一性质来找出题中的环。

        要注意常规的拓扑排序一般用于有向无环图中,入队条件是节点入度为0,再寻找它指向的节点。而此题将条件修改为度数=1,不断地寻找与其相连的节点,最后剩下的节点度数不为1,则一定在环中。

from collections import defaultdict,deque
d=defaultdict(int)                            #度数
used=defaultdict(int)                         #是否已排序
tree=defaultdict(list)
n=int(input())

for i in range(n):
    u,v=map(int,input().split())
    tree[u].append(v)
    tree[v].append(u)
    d[u]+=1
    d[v]+=1

a=[]
for i,j in d.items():
    if j==1:                                    #找出度数为1的节点
        a.append(i)
        used[i]=1

q=deque(a)
while q:
    u=q.popleft()
    for v in tree[u]:
        if not used[v]:
            d[v]-=1                            #从图中去掉u节点            
            if d[v]==1:                        
                q.append(v)
                used[v]=1

res=[]
for i,j in d.items():
    if j!=1:
        res.append(i)
res.sort()
print(" ".join(map(str,res)))

题解1.2:并查集 +DFS

       如果两个点的父节点是一样的,说明这两个点一定在环上,用其中一个当起点,另一个当终点,DFS搜索环中所有点。

from collections import defaultdict
n=int(input())
p=[i for i in range(n+1)]
used=[0]*(n+1)
tree=defaultdict(list)
ans=[0]*n

def find(x):
  if p[x]!=x:
    p[x]=find(p[x])
  return p[x]

def dfs(pos,idx):
  ans[idx]=pos
  if pos==end:
    res=sorted(ans[:idx+1])
    print(" ".join(map(str,res)))
    return
  for v in tree[pos]:
    if not used[v]:
      used[v]=1                        #注意回溯
      dfs(v,idx+1)
      used[v]=0

start=0
for i in range(n):
  x,y=map(int,input().split())
  tx,ty=find(x),find(y)
  if tx==ty:
    start,end=x,y
    break
  else:
    p[ty]=tx
    tree[x].append(y)
    tree[y].append(x)
    
used[start]=1
dfs(start,0)

题目二:蓝桥侦探

题目描述

小明是蓝桥王国的侦探。

这天,他接收到一个任务,任务的名字叫分辨是非,具体如下:

蓝桥皇宫的国宝被人偷了,犯罪嫌疑人锁定在 N 个大臣之中,他们的编号分别为 1∼N。

在案发时这 N 个大臣要么在大厅1,要么在大厅2,但具体在哪个大厅他们也不记得了。

审讯完他们之后,小明把他们的提供的信息按顺序记了下来,一共 M 条,形式如下:

  • x y,表示大臣 x 提供的信息,信息内容为:案发时他和大臣 y 不在一个大厅。

小明喜欢按顺序读信息,他会根据信息内容尽可能对案发时大臣的位置进行编排。

他推理得出第一个与先前信息产生矛盾的信息提出者就是偷窃者,但推理的过程已经耗费了他全部的脑力,他筋疲力尽的睡了过去。作为他的侦探助手,请你帮助他找出偷窃者!

输入描述

第 1 行包含两个正整数 N,M,分别表示大臣的数量和口供的数量。

之后的第 2 ∼M+1 行每行输入两个整数 x , y,表示口供的信息。

1≤N,M≤5×10^5,1≤x,y≤N

输出描述

输出仅一行,包含一个正整数,表示偷窃者的编号。

输入输出样例

输入:

4 5 
1 2
1 3 
2 3 
3 4
1 4

输出: 

2

 题解2.1:拓扑排序

        我们可以把(x,y)看成是图的一条边,这样每添加一条边就检查是否形成了环(找环思路参照1.1)。如果形成了环,那么这个人就是说谎的人。

        思路大致如上,但实操大概率超时,有精力的读者请自行验证。

题解2.2:并查集 

        这里用到的并不是一般的并查集(只能维护“朋友的朋友是朋友”的关系),而是种类并查集(“敌人的敌人也是朋友”)

        对于一个个体a,假设存在与a对立的个体!a,如果b与a对立,那么b与!a在同一并查集(朋友),a与!b也在同一并查集;反之如果b与a是朋友,那么b与!a不在同一并查集(对立),即a与b在同一并查集,!a与!b在同一并查集

n,m=map(int,input().split())
ans=0
p=[x for x in range(2*n)]        #开辟一个2*n的空间,分为x和x+n(y和y+n)

def find(x):
  if p[x]!=x:
    p[x]=find(p[x])
  return p[x]

def union(x,y):
  tx,ty=find(x),find(y)
  if tx!=ty:
    p[tx]=ty

for i in range(m):
  x,y=map(int,input().split())
  if ans!=0:  break
  tx,ty=find(x),find(y)
  txn,tyn=find(x+n),find(y+n)
  if tx==ty or txn==tyn:
    ans=x
  else:
    union(tx,tyn)
    union(ty,txn)

print(ans)

若有不正确之处,烦请在评论区指出! 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Eureka!!!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值