题目1
小强开始了他的功夫特训,特训的时间为n秒,有m个木头人,每个木头人的血量为a。小强的攻击范围为b,小强每次攻击能对最多b个存活的木头人造成1点伤害,每次攻击需要1秒,当经过一次攻击后木头人的血量为0,木头人死亡。当特训结束时,小强最多能消灭多少个木头人。
输入:
T
n,m,a,b
例:
input:
1
5 5 2 2
output:
5
解释:
第一次攻击:1 1 2 2 2
第二次攻击:1 0 1 2 2
第三次攻击:0 0 0 2 2
第四次攻击:0 0 0 1 1
第五次攻击:0 0 0 0 0
分析题目(菜鸡的分析过程)
1、第二次攻击是很关键的一次选择,如果第二次攻击结果为:0 0 2 2 2,那么小强最多能消灭4个木头人,这显然不是最优选择。但是这个问题似乎将思路引入贪心:优先选择血量较多的木头人攻击。但是这种贪心的思路显然不对,事实是有时需要优先攻击血量较多的木头人,有时需要优先攻击血量较少的木头人。那么在什么样的条件下进行不同的选择呢?。。。太复杂了涉及攻击范围,小兵血量,剩余攻击次数多个条件,放弃。
2、把问题抽象成,对于当前的一个小兵,是否选择其进行攻击。问题似乎转化成动态规划。如果攻击当前小兵,则血量减一;否则继续选择下一个小兵。打表的话似乎需要一个a*b*m*m的空间存储所有可能性,问题将变得越来越复杂。是否可以使用递归进行动态规划呢?问题依然很复杂,除了动态规划打表对应的m*m一层递归外(一波攻击的最优选择),还有攻击范围b对应的第二层递归(一次攻击的最优选择),问题更复杂了。打表和递归的方法都进行了尝试,均以失败告终。
3、无论是贪心或者动态规划,都是想要模拟小强攻击的过程,即如果采用贪心或动态规划的解法,不仅可以得到最多可以消灭多少个木头人,还可以得到攻击的方案。即如果题目修改为需要进攻方案(可能有多种答案),则只能选择贪心或动态规划求解。
4、然而,题目只需要最多可以消灭多少个木头人,并不需要进攻方案。min(n*b // a, m)似乎就可以得到结果,n*b // a是最优进攻方案最多可以消灭的木头人数量。但是攻击是有限制的,每次只能选择攻击范围内的b个木头人减去一滴血,如果木头人的血量大于攻击次数,那么一个木头人都不能消灭。那么问题就可以按照如下思路求解:1、如果n < a,则返回0,否则返回min(n、*b // a, m)。(不确定正确,欢迎留言质疑)
if __name__ == "__main__":
# 读取第一行的n
num = int(sys.stdin.readline().strip())
ans = []
for i in range(num):
# 读取每一行
line = sys.stdin.readline().strip()
# 把每一行的数字分隔后转化成int列表
values = list(map(int, line.split()))
n, m, a, b = values
if n < a:
die = 0
else:
die = min(n*b // a, m)
print(die)
题目二
有一个方格游戏是这样的:给一个n*n的矩阵,矩阵每一个位置都有一个值,初始的时候在左上角出发,并获得该位置的值作为初始能量,
每次可以选择向上、向下、向左、向右四个方向中的一个,并且可以移动距离不大于K,且要保证到达的格子的值要比当前位置的格子的值要大,
否则不能移动到这个格子,当无法移动时,游戏结束。
现在想知道在所有可能的走的方案中,到达的位置的格子值相加总和最大为多少。
输入第一行一个整数T,代表有T组测试数据。
接下来先输入两个整数n和k,代表矩阵的大小以及每一步最多能走多少个格子。
接下来n行,每行n个数,代表矩阵的A的元素。
例如:
输入:
1
3 1
0 5 37
55 10 19
12 9 18
输出:
71
解释:
最大路径为:0——5——10——19——37。和为71。
分析题目(菜鸡的分析过程)
1、走矩阵,这种问题显然需要动态规划打表解决,或者广度优先遍历也可以解决。这里采用动态规划方案。对于动态规划问题,首先需要定义dp数组和dp数组元素对应的意义,这里定义dp[n][n],dp[i][j]表示从左上角走到i,j位置的最优策略。
2、对于这道题,要求到达的格子的值要比当前位置的格子的值要大,这个条件保证了行走过程中不会走回头路,即一直朝着最大的目标前进,且走过的格子不会回头再走。这是一个很重要的条件,如果没有这个条件限制,打表过程中还需要记录走过的路径,防止回头,但变换后的问题仍然可以求解。
3、动态规划问题,最重要的就是递归表达式:
当前位置(i,j),下一步位置(k,l),(k,l)在(i,j)所能走到的下一步范围。
d
p
[
k
]
[
l
]
=
m
a
x
(
d
p
[
k
]
[
l
]
,
d
p
[
i
]
[
j
]
+
a
r
r
[
k
]
[
l
]
)
,
dp[k][l] = max(dp[k][l], dp[i][j] + arr[k][l]),
dp[k][l]=max(dp[k][l],dp[i][j]+arr[k][l]),
cond. (i,j)位置可达,(i,j)可达(k,l)
为了标注不可抵达的格子,我们将dp初始化为-1;dp[0][0]=arr[0][0]。
4、上面提到,需要确定攻击范围。“每次可以选择向上、向下、向左、向右四个方向中的一个,并且可以移动距离不大于K”,则对应当前位置(i,j)为中心的十字形区域。
# dp[i][j]表示走到i,j位置的最优策略
# dp[i][j]等于0表示绝路
# i,j走到k,l
def find_max_path_v1(arr, n, K_):
dp = []
for i in range(n):
dp.append([-1] * n)
dp[0][0] = arr[0][0]
max_sum = -1
for i in range(0, n):
for j in range(0, n):
# 不能走斜线
for k in range(max(i-K_, 0), min(i+K_+1, n)):
l=j
if arr[k][l] > arr[i][j] and dp[i][j] > -1:
dp[k][l] = max(dp[k][l], dp[i][j] + arr[k][l])
if dp[k][l] > max_sum:
max_sum = dp[k][l]
for l in range(max(j-K_, 0), min(j+K_+1, n)):
k=i
if arr[k][l] > arr[i][j] and dp[i][j] > -1:
dp[k][l] = max(dp[k][l], dp[i][j] + arr[k][l])
if dp[k][l] > max_sum:
max_sum = dp[k][l]
print(dp)
return max_sum
if __name__ == "__main__":
# 读取第一行的n
num = int(sys.stdin.readline().strip())
ans = []
for i in range(num):
# 读取每一行
line = sys.stdin.readline().strip()
# 把每一行的数字分隔后转化成int列表
values = list(map(int, line.split()))
n, k = values
arr = []
for j in range(n):
line = sys.stdin.readline().strip()
arr.append(list(map(int, line.split())))
res = find_max_path_v1(arr, n, k)
print(res)
以上两个题目都没有经过AC测试,不确定正确,欢迎留言质疑。