return 是递归中联系上下层的重要点,必须要深刻了解何时用何时不用的区别
自写组合算法
dfs时,选或不选第k个数,就实现了各种组合。
打印二进制数
vis=[0]*10
def dfs(k,s):
if k==3:
print(s)
else:
vis[k]=0#选第k个数
dfs(k+1,s+'0')
vis[k]=1#不选第k个数
dfs(k+1,s+'1')
dfs(0,'')
DFS连通块
import sys
sys.setrecursionlimit(60000)
n=int(input())
mg=[]
for i in range(n):
mg.append(list(input()))
vis=[[0]*n for i in range(n)]
def dfs(x,y):
global flag
f=1
vis[x][y]=1#注意不用回溯
for dx,dy in [(1,0),(0,1),(0,-1),(-1,0)]:
nx=dx+x
ny=dy+y
if 0<=nx<n and 0<ny<=n:
if mg[nx][ny]=='#':
if vis[nx][ny]!=1:
dfs(nx,ny)
else:
f=0 #说明x,y周围不都有岸,x,y会沉下去
if f:#说明x,y周围都有岸,x,y不会沉下去
flag=1
cnt=0
for i in range(n):
for j in range(n):
if mg[i][j]=='#' and vis[i][j]!=1:
#print(vis)
flag=0
dfs(i,j)
if flag==0:#记录会沉下去的岛
cnt+=1
print(cnt)
树上DFS
树的重心
树的重心u:
以树上任意一个结点为根计算它的子树的结点数,如果结点u的最大的子树的结点数最少,那么u就是树的重心。即删除点u后得到两棵或更多棵互不连通的子树,其中最大子树的结点数最小。u是树上最平衡的点。
那么如何计算结点的数量呢,
教父
时间限制: 2000MS | 内存限制: 65536K |
描述
去年芝加哥充满了黑帮斗殴和奇怪的谋杀案。警察局长真的厌倦了所有这些罪行,并决定逮捕黑手党领导人。
不幸的是,芝加哥黑手党的结构相当复杂。已知有n个人与黑手党有关。警方追踪他们的活动有一段时间了,知道他们中的一些人正在互相交流。根据收集到的数据,警察局长建议可以将黑手党等级制度表示为一棵树。黑手党的头目教父是树的根,如果用树中的一个节点表示某个人,则其直属下属就是该节点的子节点。为了阴谋,歹徒只与他们的直接下属和他们的直接主人联系。
不幸的是,虽然警察知道歹徒的通讯方式,但他们不知道任何一对通讯人员中谁是高手。因此他们只有一棵无向的通信树,并且不知道教父是谁。
基于教父希望对黑手党有最大可能的控制的想法,警察局长提出了一个建议,教父是这样一个人,从通信树中删除它后,最大的剩余连接组件的大小尽可能小尽可能。帮助警察找到所有可能的教父,他们会逮捕他们。
输入
输入文件的第一行包含n — 被怀疑属于黑手党的人数(2 ≤ n ≤ 50 000)。让它们从 1 到n编号。
接下来的n -1 行每行包含两个整数。一对a i , b i表示歹徒a i与歹徒b i进行了通信。保证黑帮的通讯形成一棵树。
输出
打印所有被怀疑是教父的人的人数。数字必须按递增顺序打印,并以空格分隔。
样本输入
6
1 2
2 3
2 5
3 4
3 6
示例输出
2 3
import sys
sys.setrecursionlimit(300000)
def dfs(u,fa):
global maxn
global num
tmp=0
d[u]=1
for er in edges[u]:#遍历u的子节点
if er==fa:#不递归父亲
continue
dfs(er,u)#递归子节点,计算er这个子树的节点数量
d[u]+=d[er]#计算以u为根的节点数量
tmp=max(tmp,d[er])#记录u的最大子树的节点数量
tmp=max(tmp,n-d[u])#与u父亲相连的另一半节点数量
#以上计算出了u的最大连通块
#下面计算疑似教父,如果每一个节点的最大连通块比其他节点的都小,它疑似教父
if tmp<maxn:#一个疑似教父
maxn=tmp#更新‘最小的’最大连通块
num=1
ans[num]=u#把教父记录在第一个位置
elif tmp==maxn:
num+=1
ans[num]=u#如果疑似教父有多个,记录在后面
maxn=int(1e9)
n=int(input())
edges=[[] for i in range(n+1)]
d=[0]*(n+1)
ans=[0]*(n+1)
num=0
for i in range(n-1):
a,b=map(int,input().split())
edges[a].append(b)
edges[b].append(a)
dfs(1,0)
s=sorted(ans[1:num+1])#对教父排序
for i in range(num):
print(s[i],end=' ')#按顺序打印所有教父
求树的最长直径(非负权边)
进行两次DFS:
从点a开始找最长的边,其终点为b,再从点b开始找到最长的边,其终点为c,则从b到c就是最长的边,
贪心证明:将树当作绳子,将绳子拉到最长,其上面的节点会下垂,上面任意的节点所能到达的最长边的终点必定是两端点其中的一个,负责绳子一开始就不是拉着最长的状态。
import sys
sys.setrecursionlimit(300000)
def dfs(u,fa,d):#用dfs计算从u到每个子节点的距离
dist[u]=d
for er,w in edges[u]:
if er!=fa:#关键,不回头搜素父节点
dfs(er,u,d+w)
n=int(input())
edges=[[] for i in range(n+1)]
dist=[0]*(n+1)#记录距离
for i in range(n-1):
a,b,w=map(int,input().split())
edges[a].append((b,w))
edges[b].append((a,w))
dfs(1,-1,0)
s=1
for i in range(1,n+1):#找到最远的节点s,s是直径的一个端点
if dist[i]>dist[s]:
s=i
dfs(s,-1,0)#从s出发,计算以s为起点,到树上每个节点的距离
t=1
for i in range(1,n+1):#找到直径的另一个端点t
if dist[i]>dist[t]:
t=i
print(dist[t])#打印树的直径长度
DFS拓扑排序
图+有依赖关系+有向+无环=拓扑排序
欧拉路与DFS
欧拉路:从图中某个点出发,遍历整个图,图中每条边通过且只通过一次。
图中所有的点连接的边是偶数倍,或者有且仅有起点和终点是奇数边,其他点均为偶数边。
欧拉回路:起点和终点相同的欧拉路。
图中所有的点连接的边是偶数倍
欧拉路问题:是否存在欧拉路、打印出欧拉路。
DFS剪枝
m,n=map(int,input().split())
# 输入m和n
mg=[]
for i in range(n):
mg.append(list(map(int,input().split())))
numsum=0
# 初始化numsum
for i in range(n):
numsum+=sum(mg[i])
# 计算总和
vis=[[0]*m for i in range(n)]
# 初始化vis
ans=100000
# 初始化ans
def dfs(x,y,c,s):
# 定义dfs函数
global ans,numsum
# 定义全局变量ans
# 将点设置为已访问
if 2*s>numsum:
# 如果总和大于numsum,则返回
return
if 2*s==numsum:
# 如果总和等于numsum,则比较ans和c
if ans>c and vis[0][0]==1:
# 如果ans大于c,则更新ans
ans=c
# 将c赋值给ans
return
vis[x][y]=1
for dx,dy in [(1,0),(-1,0),(0,1),(0,-1)]:
# 遍历四个方向
nx=dx+x
ny=dy+y
# 计算点的坐标
if 0<=nx<n and 0<=ny<m and vis[nx][ny]!=1:
# 如果点未访问,且不是边界点,则调用dfs函数
dfs(nx,ny,c+1,s+mg[x][y])
vis[x][y]=0
# 将点设置为未访问
dfs(0,0,0,0)
# 调用dfs函数
print(ans)
超时
n=int(input())
north=list(map(int,input().split()))
west=list(map(int,input().split()))
mg=[[0]*n for i in range(n)]
vis=[[0]*n for i in range(n)]
a=0
for i in range(n):
for j in range(n):
mg[i][j]=a
a+=1
# for i in range(n):
# print(mg[i])
def cheak():
return sum(north)+sum(west)
def dfs(x,y,l):
if x==n-1 and y==n-1 and cheak()==2:
#print(11)
print(*(l+[mg[n-1][n-1]]))
return
vis[x][y]=1
west[x] -= 1
north[y] -= 1
for dx,dy in [(1,0),(-1,0),(0,1),(0,-1)]:
nx=dx+x
ny=dy+y
if 0<=nx<n and 0<=ny<n and vis[nx][ny]!=1 and west[nx]>=1 and north[ny]>=1:
dfs(nx,ny,l+[mg[x][y]])
vis[x][y]=0
west[x] += 1
north[y] += 1
dfs(0,0,[])
mg=[0]*16
vis=[0]*17
mg[0]=1
vis[1]=1
cnt=0
def dfs(c):
global cnt
if c>=4 and mg[0]+mg[1]+mg[2]+mg[3]!=34:
return
if c>=8 and mg[4]+mg[5]+mg[6]+mg[7]!=34:
return
if c>=12 and mg[8]+mg[9]+mg[10]+mg[11]!=34:
return
if c>=13 and (mg[0]+mg[4]+mg[8]+mg[12]!=34
or mg[3]+mg[6]+mg[9]+mg[12]!=34):
return
if c>=14 and mg[1]+mg[5]+mg[9]+mg[13]!=34:
return
if c>=15 and mg[2]+mg[6]+mg[10]+mg[14]!=34:
return
if c>=16 and (mg[12]+mg[13]+mg[14]+mg[15]!=34
or mg[3]+mg[7]+mg[11]+mg[15]!=34
or mg[0]+mg[5]+mg[10]+mg[15]!=34):
return
if c==16:
print(cnt)
cnt+=1
return
for i in range(2,17):
if vis[i]!=1:
mg[c]=i
vis[i]=1
dfs(c+1)
mg[c]=0
vis[i]=0
# dfs(1)
# print(cnt)
print(416)
超时
def dfs(x,room):
global num,p
if room>=num:#剪枝
return
if x>n:
num=min(room,num)#更新最优解
return
for j in range(1,room+1):#枚举考场,把第x个人放到第i个考场里面
k=0#第k个座位
while p[j][k] and a[x][p[j][k]]==0:#如果k位子有人而且不认识x
k+=1#下一个位子
if p[j][k]==0:
p[j][k]=x#第j个考场的第k个位子让第x个学生坐
dfs(x+1,room)#继续
p[j][k]=0#回溯
p[room+1][0]=x#如果1-room的考场都不能坐,就到第room+1个考场的第一个位子
dfs(x+1,room+1)
p[room+1][0]=0#回溯
n=int(input())
m=int(input())
num=110
a=[[0 for j in range(n+1)]for i in range(n+1)]#关系表
p=[[0 for j in range(n+1)] for i in range(n+1)]#考场状态
for i in range(m):
u,v=map(int,input().split())
a[u][v]=1
a[v][u]=1#表示x和y认识
dfs(1,0)
print(num)
n=int(input())
def dfs(s,n):
if s in dis.keys():#如果状态s在dis中出现过,直接返回其值
return dis[s]
# 因为是小明先下棋,故先判断棋面是否能直接得出结果
if "*OL" in s or 'LO*' in s or 'L*L' in s:
dis[s] = 1#将此状态加入dis中并幅值
return 1
if 'LOL' in s:
dis[s] = -1
return -1
if '*' not in s and 'LOL' not in s:
dis[s] = 0
return 0
#无法直接得出结果的话,
flag=-1#如果下一步的所有操作都没有1或是0,则必定输
for i in range(n):#对每个位置进行遍历,即下一步的所有可能操作
l = list(s)#修改需先转换为列表
if l[i]=='*':#可以修改
l[i]='L'#若修改为L
r=dfs(''.join(l),n)#结果需要下一次递归得到
if r==-1:#因为小明先下的,故进入下一次循环后则为k下,若返回值r=-1,说明k输了,则小明嬴幅值为1
dis[s]=1
return 1#在每次递进时,如果有小明为1的情况则提前结束,直接递归,没必要继续递进下去
if r==0:#k平局,说明小明也是平局,flag赋值为0
flag=0
#if r==1:对于为什么没有这种判断情况,我们可以想想之前for循环是遍历下一步的所有走法,
#假设即出现0又出现-1我们取什么给dis[s]呢?答案是肯定的,我们应该取0,即选择持平的走法,而不是-1,
#如果写上这个判断,for循环最后一次如果是-1,那么前面无论有没有0,结果都是-1,这个答案肯定是错误的
l=list(s)
l[i]='O'#若修改为O同理
r=dfs(''.join(l),n)
if r==-1:
dis[s]=1
return 1
if r==0:
flag=0
dis[s]=flag
return dis[s]
for i in range(n):
s=input()
dis={}#字典可以满足查重,并返回值
print(dfs(s,len(s)))
题目思路类似填字母游戏,难点在于状态保存和查重,因为双方拿球会诞生出三个袋子,即两个放球的袋子和一个拿球的袋子状态,故不能向填字母一样,填字母可以看成都作用在一个袋中,所以正反手时,需要转换传入的顺序,因为奇数都可以用%2=1表示,偶数都可以用%2=0表示,故可以减小数组的空间。
注意:本题必须要用这三个袋子来表示状态,缺一不可。
这道题很迷,做出来也不是很懂
def dfs(num,a,b):
if dis[num][a][b]!='':
return dis[num][a][b]
if num<min(n1,n2,n3):
if a==b:
dis[num][a][b]='0'
return '0'
if a==1:
dis[num][a][b] = '+'
return '+'
else:
dis[num][a][b] = '-'
return '-'
flag='-'
for i in [n1,n2,n3]:
if num>=i:
res=dfs(num-i,b,(a+i)%2)
if res=='-':
dis[num][a][b]='+'
return '+'
if res=='0':
flag='0'
dis[num][a][b]=flag
return dis[num][a][b]
n1,n2,n3=map(int,input().split())
nums=list(map(int,input().split()))
for i in range(5):
n=nums[i]
dis = [[['']*2 for j in range(2)] for i in range(n+1)]
print(dfs(n,0,0),end=' ')
A,B=map(int,input().split())
n=A+B
t=0
for i in range(n):
if i*(i+1)//2==n:
t=i
break
# print(t)
def dfs(s,numa,numb,temp):
if numa<0 or numb<0:#不合法
return False
if temp==0:
return numa==numb==0 #都等于0说明合法
b=bin(s)[2:].count('1') #规定二进制形式0b0001中1的个数就是b的个数
#注意,多余的0会被省去,所以只能先求出1的数量
a=temp-b#第temp层就有temp个数
ns=(s^(s>>1))&((1<<(temp-1))-1) #异项得A,同向得B,与异或运算相似,
#可以用二进制的异或运算来得出第temp-1层的状态ns
#而s右移一位后与s异或就是s相邻异或的结果,因为得出的结果还是temp个位数,
#所以将其与其长度为tmep-1的011111想与,从而得到第temp-1层的数
return dfs(ns,numa-a,numb-b,temp-1)
cnt=0
for i in range(1<<t):#最低层确定后,整个塔就确定了
if dfs(i,A,B,t):#所以对底层的每一个状态都dfs确定是否合法
cnt+=1
print(cnt)