目录
题目一:发现环
题目描述
小明的实验室有 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)
若有不正确之处,烦请在评论区指出!