重温DFS

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值