因为并查集的特性导致它只能合并点而不能删点 所以有些题需要逆向来思考
每次增加一个点联通块增加一,然后与其相连的可访问的点进行并查集联通,若不属于同一个根,则进行联通,连通块减一
前两题比较简单,后面的需要逆向思考
P1111 修复公路
n,m=map(int,input().split())
la=[]
for i in range(m):
a,b,c=map(int,input().split())
la.append((a,b,c))
fa=[i for i in range(n+1)]#初始化
def find(x):
if fa[x]==x:
return x
else:
fa[x]=find(fa[x])
return fa[x]
ans=0
mi=float("-inf")
la.sort(key=lambda x:x[2])
flag=0
for l in la:
x=find(l[0])
y=find(l[1])
if x!=y:
fa[x]=y
ans=ans+1
mi=l[2]
if ans==n-1:
flag=1
break
if flag==1:
print(mi)
else:
print(-1)
P1551 亲戚
n,m,c=map(int,input().split())
fa=[i for i in range(n+1)]
def find(x):
if fa[x]==x:
return x
else:
fa[x]=find(fa[x])
return fa[x]
for i in range(m):
a,b=map(int,input().split())
q=find(a)
p=find(b)
if q!=p:
fa[q]=p
la=[]
for i in range(c):
a,b=map(int,input().split())
la.append((a,b))
for l in la:
q = find(l[0])
p = find(l[1])
if q != p:
print("No")
else:
print("Yes")
P1197 [JSOI2008] 星球大战
"""因为并查集的特性导致它只能合并点而不能删点 所以需要逆向来思考"""
n,m=map(int,input().split())#逆向思维,python 会超时,四个
e=[[] for i in range(n)]
for i in range(m):#建立双向边
a,b=map(int,input().split())
e[a].append(b)
e[b].append(a)
k=int(input())
br=[]
vis=[0]*n
for i in range(k):#被破坏的星球,并标记
c=int(input())
br.append(c)
vis[c]=1
cnt=n-k#没被破坏的星球
fa=[i for i in range(n)]#初始化
def find(x):#查找父亲节点
if fa[x]==x:
return x
else:
fa[x]=find(fa[x])
return fa[x]
def bingcha1(u):#精髓
global cnt
for i in e[u]:
if vis[i]:#被破坏的星球,不进行联通
continue
x=find(u)
y=find(i)
if x!=y:
fa[x]=y
cnt=cnt-1#连通块减一
def bingcha2(u):
global cnt
if vis[u]:#将被破坏的星球,变成未破坏的,加上一个点,联通块加一
cnt=cnt+1
vis[u]=0
for i in e[u]:
if vis[i]:#被破坏的星球,不进行联通
continue
x=find(u)
y=find(i)
if x!=y:
fa[x]=y
cnt=cnt-1#连通块减一
for i in range(n):
if not vis[i]:bingcha1(i)#没被破坏的星球,进行并查集
res=[]#结果集
res.append(cnt)
for i in range(k-1,-1,-1):
bingcha2(br[i])
res.append(cnt)
for k in res[::-1]:
print(k)
P3144 [USACO16OPEN]Closing the Farm S
"""将无法访问的点标记,每次增加一个点联通块增加一,然后与其相连的可访问的点进行并查集
联通,若不属于同一个根,则进行联通,连通块减一"""
n,m=map(int,input().split())#要用逆向思维,逐个点加进去
e=[[] for i in range(n+1)]
for i in range(m):#存储
a,b=map(int,input().split())
e[a].append(b)
e[b].append(a)
vis=[0]*(n+1)
lc=[]
for i in range(n):
c=int(input())
vis[c]=1
lc.append(c)
fa=[i for i in range(n+1)]
def find(x):
if fa[x]==x:
return x
else:
fa[x]=find(fa[x])
return fa[x]
def liantong(v):
global cnt
cnt=cnt+1#加上一个点,连通块加上一
vis[v]=0#将其标记为可访问
for i in e[v]:#与其相连,且可访问的点进行并查集,
if vis[i]==1:
continue
x=find(v)
y=find(i)
if x!=y:#若不属于同根,则用其根节点指向另一个根节点,并将连通块个数减一
fa[x]=y
cnt=cnt-1
cnt=1
vis[lc[-1]]=0
lc.pop()#首先处理倒数第一个数
jieguo=["YES"]
for v in lc[::-1]:
liantong(v)
if cnt==1:
jieguo.append("YES")
else:
jieguo.append("NO")
for i in jieguo[::-1]:#逆向输出
print(i)
P1536 村村通
"""并查集形成的是树,树的性质n个节点,n-1边,两两节点相互联通,由性质推每次find(a)!=find(b)时形成一条边
答案即是n-1-cnt,cnt是形成的边数"""
res=[]#答案
while True:
l=list((map(int,input().split())))
if len(l)==1:
break
fa = [i for i in range(l[0]+1)]
def find(x):
if x==fa[x]:
return x
else:
fa[x]=find(fa[x])
return fa[x]
cnt=0
for i in range(l[1]):
lw=list(map(int,input().split()))
c=find(lw[0])
d=find(lw[1])
if c!=d:
fa[c]=d
cnt=cnt+1
res.append(l[0]-1-cnt)
for i in res:
print(i)