蓝桥杯-砝码称重
问题描述:
你有一架天平和 N 个砝码,这 N 个砝码重量依次是 W1, W2, · · · WN
请你计算一共可以称出多少种不同的重量?
注意砝码可以放在天平两边
输入的第一行包含一个整数 N
第二行包含 N 个整数:W1, W2, W3, · · · WN输出一个整数代表答案
输出一个整数代表答案
样例输入
3
1 4 6
样例输出
10
这篇文章主要是分析动态规划解决此类问题。
不用动态规划,这样的问题也可以解决,比如用枚举法
##枚举法,时间超时
while True:
try:
N = int(input())
W = list(map(int,input().split()))
nums = W.copy()
dp = [[0]]
for i in range(N):
dp_list = []
for j in range(len(dp[i])):
temp_dp1 = dp[i][j]+W[i]*1
temp_dp2 = abs(dp[i][j]+W[i]*(-1))
temp_dp3 = dp[i][j]+W[i]*0
if temp_dp1 not in nums:
nums.append(temp_dp1)
if temp_dp2 not in nums:
nums.append(temp_dp2)
if temp_dp3 not in nums:
nums.append(temp_dp3)
dp_list.extend([temp_dp1,temp_dp2,temp_dp3])
dp.append(dp_list)
print(len(nums)-1)
except:
break
但这个时间是超时的,因为枚举法消耗资源过多,算法复杂度随着N的增大,呈现指数增长,其中,我列举了3中状态,对于每个砝码他可以选择放在左边、右边、或者不放。
接下来我们来看动态规划解法
N = int(input())
weights = list(map(int,input().split()))
maxlen = sum(weights) #这是天平所能称重的最大范围
dp = [[0]*(maxlen*2+1) for i in range(N+1)] #多生成一列和一行
for i in range(N+1):
dp[i][0] = 1
for i in range(1,N+1):
for j in range(maxlen+1): #首先初始化最边际地带
if dp[i-1][j] == 1: #如果前i-1能取到j
dp[i][j] = 1 ##前i-1个能取到,前i个一定能取到
dp[i][j+weights[i-1]] = 1
dp[i][abs(j-weights[i-1])] = 1
print(sum(dp[N])-1) #多一个0
动态规划能解决的问题,有这样的特征,有重复计算值、上一子问题可以作为当前子问题的提取库。
N = int(input())
weights = list(map(int,input().split()))
maxlen = sum(weights) #这是天平所能称重的最大范围
dp = [[0]*(maxlen*2+1) for i in range(N+1)]
一般dp是要定义一个多一行一列的取值库,不过对此问题而言,需要变动一下成二倍列,多一行的取值库,为了防止超出索引。
for i in range(1,N+1):
for j in range(maxlen+1): #首先初始化最边际地带
if dp[i-1][j] == 1: #如果前i-1能取到j
dp[i][j] = 1 ##前i-1个能取到,前i个一定能取到
dp[i][j+weights[i-1]] = 1
dp[i][abs(j-weights[i-1])] = 1
print(sum(dp[N])-1) #多一个
动态规划主要切入点是分析dp[i]与dp[i-1]的关系,有啥关心呢?
- dp[i][j]代表的是前i+1项能否取到j值,能则为1否者为0,那么dp[i-1][j]为1,dp[i][j]一定为1,这样可以得到关系dp[i-1][j]=dp[i][j],条件前者为1
根据题意,前面提到-对于每个砝码他可以选择放在左边、右边、或者不放,这咋体现呢?
放右边边:dp[i][j+weights[i-1]],一开始小白会???????,这怎么回事呢?j+weigths不会超额吗?答案是不会,因为前面条件的限制,dp[x][j=maxlen]会在最后出现。
放左边:dp[i][abs(j-weigths[i-1])],涉及到减号,加上abs
不放:过程中已经体现了,不信你频频
动态规划核心是前i!前i!前i!,啥题目都这样。