蓝桥杯备赛(三)— 数学与简单DP
概念
数学:主要指的是数论中的方法(余数相关,最大公约数,质数筛 and so on)。
DP:主要是通过总结过程中的规律,从而由小到大推出结果。
实例
Q1
买不到的数目(原题链接)
A1
该题可以利用数学结论,两个互质的数,凑不出的最大的数为(m-1)(n-1)-1。同时,我们也可以利用DP的方式进行求解。
DP在这主要是如果当前数可以被凑出,则其减去这两个数也能被凑出,故这里可以引入DP进行状态转移。
代码如下:
m,n=map(int,input().split())
print((m-1)*(n-1)-1)
n,m=map(int,input().split())
dp=[False for _ in range(int(1e6)+10)]
dp[0]=True
max_=max(n,m)
min_=min(n,m)
ans=0
for i in range(min_,int(1e6)+10):
if dp[i-min_]:
dp[i]=True
elif i>=max_ and dp[i-max_]:
dp[i]=True
else:
ans=i
print(ans)
Q2
蚂蚁感冒(原题链接)
A2
首先我们必须要明白两只蚂蚁相撞掉头可以看作时一只蚂蚁穿过了另一只蚂蚁,因为相撞之后两只蚂蚁都感冒了,掉不掉头其实无所谓,毕竟都感冒了,这样的话这题就简单多了。我们先不考虑特殊情况,先来看看一般情况:
第一只蚂蚁不管方向朝哪里,只要它右边的蚂蚁向左走就可能碰撞感染,同样,第一只蚂蚁左边的蚂蚁只要朝右边走也可能被感染,这样就很容易得到ans=right+left+1。这里left表示左边蚂蚁向右走的数量,right表示右边蚂蚁向左走的数量,1是指第一只蚂蚁本身。
还有一种特殊情况,就是当第一只蚂蚁向左走的时候,如果第一只蚂蚁左边没有向右爬行的蚂蚁,由于爬行速度相同,所以不管第一只蚂蚁右边有多少向左爬行的,其右边的蚂蚁永远不可能被感染。同理,当第一只蚂蚁向右走的时候,如果第一只蚂蚁右边没有向左爬行的蚂蚁,其左边也永远不可能感染。
代码如下:
n=int(input())
pixvot_list=list(map(int,input().split(' ')))
left=0
right=0
for i in range(1,len(pixvot_list)):
if (abs(pixvot_list[i])>abs(pixvot_list[0]))&(pixvot_list[i]<0):
right+=1
elif (abs(pixvot_list[i])<abs(pixvot_list[0]))&(pixvot_list[i]>0):
left+=1
if (left==0 or right==0) and n>2:#这里是避免只有两只的时候
print('1')
else:
print(str(left+right+1))
Q3
饮料换购(原题链接)
A3
超级简单的数学递推问题。
代码如下:
ans=int(input())
gaizi=ans
while gaizi//3:
ans+=gaizi//3
gaizi=gaizi//3+gaizi%3
print(ans)
Q4
01背包问题(原题链接)
A4
经典DP问题,f[i][j]代表前i个物体在容积为j的价值最大值。
代码如下:
n,v_=map(int,input().split())
value_lst=[0]
volume_lst=[0]
for _ in range(n):
v,w=map(int,input().split())
value_lst.append(w)
volume_lst.append(v)
dp=[[0 for _ in range(v_+1)]for _ in range(n+1)]
for i in range(1,n+1):
for j in range(1,v_+1):
if volume_lst[i]>j:
dp[i][j]=dp[i-1][j]
else:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-volume_lst[i]]+value_lst[i])
print(dp[n][v_])
Q5
摘花生(原题链接)
A5
很经典的简单dp问题。dp[i][j]=max(dp[i-1][j],dp[i][j-1])+mat[i][j]
代码如下:
T=int(input())
while T:
r,c=map(int,input().split())
mat=[[0 for _ in range(c+1)] for _ in range(r+1)]
for i in range(1,r+1):
mat[i][1:]=list(map(int,input().split()))
#print(mat)
dp=[[0 for _ in range(c+1)] for _ in range(r+1)]
for i in range(1,r+1):
for j in range(1,c+1):
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+mat[i][j]
print(dp[r][c])
T-=1
Q6
最长上升子序列(原题链接)
A6
线性DP模板题,dp[i]与每个之前的数都有关。其存的是以i结为的,从0开始算起的最长子序列的长度。
代码如下:
n=int(input())
lst=list(map(int,input().split()))
dp=[1 for _ in range(n)]
for i in range(n):
for j in range(i):
if lst[j]<lst[i]:
dp[i]=max(dp[i],dp[j]+1)
print(max(dp))
Q7
地宫寻宝(原题链接)
A7
这道题主要难度在如何构建DP状态方程。
dp[i][j][k][v]:代表的是在(i,j)处,当前宝贝数量为k,且最大价值为v的方案数量。于是递推关系如代码所示。
代码如下:
n,m,k=map(int,input().split())
mod=1000000007
#为了与不选区分开,我们将价值为0的物品视为1
mat=[list(map(lambda x:int(x)+1,input().split())) for _ in range(n)]
dp=[[[[0 for _ in range(14)]for _ in range(k+1)]for _ in range(m+1)]for _ in range(n+1)]
dp[1][1][0][0]=1#第一个不选
dp[1][1][1][mat[0][0]]=1 #第一个选
res=0
for i in range(1,n+1):
for j in range(1,m+1):
for cnt in range(k+1):
for v in range(14):
#当前位置不选
dp[i][j][cnt][v]=(dp[i][j][cnt][v]+dp[i-1][j][cnt][v])%mod
dp[i][j][cnt][v]=(dp[i][j][cnt][v]+dp[i][j-1][cnt][v])%mod
#当前位置选
if cnt>0 and v==mat[i-1][j-1]:
for s in range(v):
dp[i][j][cnt][v]=(dp[i][j][cnt][v]+dp[i-1][j][cnt-1][s])%mod
dp[i][j][cnt][v]=(dp[i][j][cnt][v]+dp[i][j-1][cnt-1][s])%mod
for i in range(14):
res=(res+dp[n][m][k][i])%mod
print(res)
Q8
波动数列(原题链接)
A8
这道题难吐了,关键在于理解同余。害。
设这个数列第一项为x,设 d ∈{ +a, -b } ,则长度为n的序列所有的项为
x,x+d1,x+d2,x+d3,…,x+dn−1
他的和为 nx+(n−1)d1+(n−2)d2+(n−3)d3+…+dn−1=s
转换为x=(s−((n−1)d1+(n−2)d2+(n−3)d3+…+dn−1))/n,由于x不确定,我们可以转换为d1到dn的不同组合和与S对n取模同余即可。
此时我们可以设dp[i][j]为遍历到第i项,当前和对n取余数为j的方案数。故我们只需要选取dp[n][get_mod(s,n)]项即可
其状态转移方程具体见代码。
代码如下:
MOD=100000007
[n,s,a,b]=list(map(int,input().split(' ')))
def get_mod(a,b):
return (a%b+b)%b #求a除以b的正余数
#开一个dp数组
dp=[[0 for _ in range(n)] for _ in range(n+1)]
dp[0][0]=1#其他肯定都为0啦
for i in range(1,n):
for j in range(n):
dp[i][j]=(dp[i-1][get_mod(j-i*a,n)]+dp[i-1][get_mod(j+i*b,n)])%MOD
print(dp[n-1][get_mod(s,n)])
总结
难的雅痞。背包问题需要多研究研究。同时需要注意问题的变换,很重要。