1. 问题描述:
翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。翰翰和达达只好花钱让它们坐索道下山。索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。当然,每辆缆车上的小猫的重量之和不能超过 W。每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?
输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。
输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
数据范围
1 ≤ N ≤ 18,
1 ≤ Ci ≤ W ≤ 10 ^ 8
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2
来源:https://www.acwing.com/problem/content/description/167/
2. 思路分析:
分析题目可以知道我们需要搜索出所有放置小猫的缆车方案,在所有的方案中找到放置小猫的最少缆车数量,因为数据规模比较小而且需要在所有方案中找最优解所以可以使用dfs来搜索,使用dfs搜索的时候首先需要解决的问题是如何搜索才可以将所有的方案搜索出来,也即想一个搜索顺序将所有方案枚举出来,一个比较容易想到的枚举顺序:枚举每一只小猫将其放到哪一个缆车上,在当前缆车数量的前提下枚举放到哪一辆缆车上(枚举当前可以放到之前已经存在的缆车上),如果可以放那么就将小猫放到缆车上,枚举完所有的缆车之后或者是新开一辆缆车来放置当前的小猫,可以使用一个s数组来存储当前每一辆缆车的小猫的重量之和,用来判断当前小猫是否可以放到缆车上,本质上与1118题是一模一样的,都是一样的思路,第二个是通过剪枝对dfs进行优化,一般有五个剪枝策略,对于这道题目来说有三个策略,第一个是优化搜索的顺序,我们可以从重量较大的小猫开始枚举(先对猫的重量从小到大排序),这样枚举的分支数量会比较少,第二个是可行性剪枝,如果当前小猫可以放到缆车上才放置,第三个是最优性剪枝,当前缆车的数量大于之前搜到的缆车数量那么直接return即可,因为当前搜索下去毫无意义不会小于之前搜到的最优解。
3. 代码如下:
from typing import List
class Solution:
res = 0
# u表示当前搜索的是第u只小猫, g表示当前的缆车数量, n表示小猫的数量, m表示一辆缆车承受的最大重量, w为质量, s存储没一栏缆车放的小猫重量
def dfs(self, u: int, g: int, n: int, m: int, w: List[int], s: List[int]):
# 最优性剪枝
if g >= self.res: return
# 最后一只小猫
if u == n:
self.res = g
return
# 枚举放到之前的已经有的缆车上
for i in range(g):
# 可行性剪枝
if s[i] + w[u] <= m:
s[i] += w[u]
self.dfs(u + 1, g, n, m, w, s)
# 回溯
s[i] -= w[u]
# 尝试新开一辆缆车
s[g] += w[u]
self.dfs(u + 1, g + 1, n, m, w, s)
# 回溯
s[g] -= w[u]
def process(self):
# n表示小猫的数量, m表示每一辆缆车的最大重量
n, m = map(int, input().split())
w = list()
for i in range(n):
w.append(int(input()))
# 存储每一辆缆车放的小猫的重量
s = [0] * (n + 10)
# 最多每一只小猫分配一辆缆车
self.res = n
# 从大到小排序, 从重量较大的开始枚举
w.sort(reverse=True)
self.dfs(0, 0, n, m, w, s)
return self.res
if __name__ == '__main__':
print(Solution().process())