蓝桥杯备赛(1) — 递归与递推
概念
递归:函数存在本身调用函数本身的情况,叫做递归。递的意思是将问题拆解成子问题进行求解,子问题再进一步求解,直到无法进一步细致。归的意思就是最小子问题的求解。递归解题通用解决思路:1.一个问题可以分解成具有相同解决思路的子问题(本质就是能调用同一个函数dfs)2.经过层层分解的子问题最后一定是有一个不能再分解的固定值的(即终止条件)。当具备以上条件时,我们即可在一定T复杂度下使用递归实现。
递推:通过一定的公式或者堆砌将我们的结果从底层开始进行递推。
例题实操
Q1
递归实现指数型枚举(点击此处跳转原题)
A1
对于该问题,我们每个数字都可以是选与不选两种情况。我们不妨将0视为不选,1视为选。故我们可以利用二进制的表达来代替每个答案的结果。
比如:8 -->1000 ->第一个数选,其余数均不选。
我们可以通过位运算来获取这种结果
代码如下
n=int(input())
for i in range((1<<n)):
lst=[]
for k in range(n):
if i>>k & 1:
#说明该位为1
lst.append(str(k+1))
if len(lst)==0:
print(' ')
else:
print(' '.join(lst))
这是我们对于题目进行理解得到的结果,但我们在考场上可能难以第一时间想到这种简洁的方式(bushi)。我们可以联想到我们的递推。通过逐个递推每个数字是否使用,直到达到最后一个数字system.out。
代码如下:
n=int(input())
st_list=[0 for i in range(n+1)]#存储每个数是否被利用
def dfs(k):
#k:当前遍历的数字
if k>n:
str_lst=[]
for i in range(1,n+1):
if st_list[i]:
str_lst.append(str(i))
print(' '.join(str_lst))
return
st_list[k]=1
dfs(k+1)#用k
st_list[k]=0#恢复现场
dfs(k+1)#不用k
dfs(1)
两种代码均可完全AC
Q2
递归实现排列型枚举(点击此处跳转原题)
A2
典型的萝卜填坑问题,我们可以利用st_lst来存储每个位置放置的数,同时judge_lst来存储每个数据是否被利用。通过由小到大的顺序来进行dfs以满足字典序。
代码如下
n=int(input())
st_lst=[0 for i in range(n+1)]#每个位置存储的数列表
judge_lst=[0 for i in range(n+1)]
def dfs(k):
if k>n:
print(' '.join(list(map(str,st_lst[1:]))))
return
for i in range(1,n+1):
if judge_lst[i]==0:
st_lst[k]=i
judge_lst[i]=1
dfs(k+1)#下一层遍历
judge_lst[i]=0#恢复原状,不然全部都是exist
dfs(1)
Q3
费解的开关(点击此处跳转原题)
A3
针对该问题,我们需要从题目特征中进行分析,开关开两次相当于啥也没做,故我们对每个开关只需动一次就行,而且当我们从上往下动时,每一个只会影响其上面的,最后只需特判最后一行是否满足全亮即可(同时需要关注是否在六步之内)。于是这就是我们最典型的递推问题。
代码如下:
from copy import deepcopy
n=int(input())
dx=[0,0,1,0,-1]
dy=[0,-1,0,1,0]
def change(x,y):
for i in range(5):
a=x+dx[i]
b=y+dy[i]
if a>=0 and a<5 and b>=0 and b<5:
mat_copy[a][b]^=1#取反操作
for _ in range(n):
cnt=10
drag_mat=[list(map(int,list(input()))) for _ in range(5)]
#这里由于第一行的特殊情况,我们需要进行枚举,不然的话就不一定是最优解
#因为我们是从第二行开始操作的,故需要枚举第一行的操作。
for num in range(32):
#进行深复制,防止对原来的Mat造成影响
mat_copy=deepcopy(drag_mat)
ans=0
for k in range(5):
if (num>>k)&1:
change(0,k)
ans+=1
for i in range(4):
for j in range(5):
if mat_copy[i][j]==0:
change(i+1,j)
ans+=1
#对最后一行进行特判
for j in range(5):
if mat_copy[4][j]==0:
ans=10
cnt=min(cnt,ans)
if cnt>6:
cnt=-1
print(cnt)
try:
_=input()
except:
break
Q4
递归实现组合型枚举(点击此处跳转原题)
A4
该题可借鉴排列型枚举的思路,萝卜填坑。
代码如下:
n,m=map(int,input().split())
num=[]#引入动态数组存储结果
def dfs(k):
if len(num)>m or len(num)+(n-k+1)<m:
#长度超过m或者剩余的数无法到达m,则采取剪枝,break即可
return
if k==n+1:
print(' '.join(list(map(str,num))))
num.append(k)#选择k
dfs(k+1)
#回溯
num.remove(k)
dfs(k+1)#不选择k
dfs(1)
#体会一下动态数组的强大!
Q5
带分数(点击此处跳转原题)
A5
解法1:该题典型的全排列问题,我们可以首先进行全排列,然后进行a|b|c的形式进行分割,然后进行判决。
代码如下:
n=int(input())
count=0
exit_list=[0 for i in range(9)]
num_list=[0 for i in range(9)]
def calc(l,r):
res=0
for i in range(l,r+1):
res=res*10+num_list[i]
return res
def dfs(num):
global count
if num==9:
for i in range(7):
a=calc(0,i)
if a>n:
continue
for j in range(i+1,8):
b=calc(i+1,j)
c=calc(j+1,8)
if (a*c+b==c*n):
#转换成乘法(算法中常用的思想)
count+=1
return
for i in range(9):
if not exit_list[i]:
exit_list[i]=1
num_list[num]=i+1
dfs(num+1)
exit_list[i]=0#回到初始状态
dfs(0)
print(count)
解法二:我们可以先对a进行排列型枚举,再对b进行,然后通过判决等式进行剪枝(c是否满足我们的条件:全选且不重复)。思路简单,但实操起来代码是比较难理解的。
代码如下:
N=9
n=int(input())
had_use=[0 for i in range(N)]
ans=0
def check(a,c):
b=n*c-a*c
ever=had_use.copy()
while b:
#对b的每一位进行判断
t=b%10#取它最后一位,来获取每一位的值(取余操作)
b//=10#(将最后一位去掉)
if (t==0)|(ever[t-1]):
return False
ever[t-1]=1
for i in range(N):
if not ever[i]:
return False
return True
def dfs_c(x,a,c):
global ans,had_use
if x>N:#在这里判断是否超限制,妙!
return
if check(a,c):
ans+=1
for i in range(N):
if not had_use[i]:
had_use[i]=1
dfs_c(x+1,a,c*10+i+1)
had_use[i]=0
def dfs_a(x,a):
global had_use
if a>=n:
return
if a:
dfs_c(x,a,0)
for i in range(N):
if not had_use[i]:
had_use[i]=1
dfs_a(x+1,a*10+i+1)
had_use[i]=0
dfs_a(0,0)
print(ans)
Q6
飞行员兄弟(点击此处跳转原题)
A6
这题思路与费解的开关一致,并未变得更加复杂。但调试代码比较麻烦,不容易一次性写对。
代码如下:
bridge_matrix=[]
mat=[]
count=10000
num=0
for i in range(4):
mat.append(list(input()))
def change(i,j):
global bridge_matrix
for t in range(4):
bridge_matrix[i][t]='-'if bridge_matrix[i][t]=='+' else '+'
bridge_matrix[t][j]='-'if bridge_matrix[t][j]=='+' else '+'
bridge_matrix[i][j]='-'if bridge_matrix[i][j]=='+' else '+'
def check():
for i in range(4):
for j in range(4):
if bridge_matrix[i][j]=='+':
return False
return True
def dfs():
global bridge_matrix,count,num
for i in range(1,2**16):
#print(i)
bridge_matrix=mat.copy()
t=0
for j in range(15,-1,-1):
state=(i>>j)&1
ind=(15-j)//4
col=(15-j)%4
if state:
change(ind,col)
t+=1
if check()and(t<count):
num=i+1
count=t+1
print(count)
for j in range(15,-1,-1):
state=(num>>j)&1
ind=(15-j)//4
col=(15-j)%4
if state:
print(str(ind+1)+' '+str(col+1))
dfs()
Q7
翻硬币(点击此处跳转原题)
A7
该题与费解的开关也很类似,而且更为简单。我们也可以利用递推来求解。做这种题就应该想到我们的操作顺序以及次数是否会对原有state有何影响。若满足操作顺序、偶数次数没影响时,阔以利用递推的思维来进行判断
代码如下:
str_init=list(input())
str_target=list(input())
count=0
def turn(i):
global str_init
for t in range(i,i+2):
str_init[t]='*' if str_init[t]=='o' else 'o'
def check(i):
if str_init[i]==str_target[i]:
return 0
return 1
def dfs():
global count
for i in range(len(str_target)-1):
if check(i):
turn(i)
count+=1
dfs()
print(count)
总结
我们可以得知,递推和递归的思想可以用来求解操作方式单一、问题可以逐步化解、操作顺序对结果无影响的问题。当然个人总结比较片面,仅供参考。