牛牛准备参加学校组织的春游, 出发前牛牛准备往背包里装入一些零食, 牛牛的背包容量为w。
牛牛家里一共有n袋零食, 第i袋零食体积为v[i]。
牛牛想知道在总体积不超过背包容量的情况下,他一共有多少种零食放法(总体积为0也算一种放法)。
输入描述
输入包括两行
第一行为两个正整数 n n n和 w ( 1 < = n < = 30 , 1 < = w < = 2 ∗ 1 0 9 ) w(1 <= n <= 30, 1 <= w <= 2 * 10^9) w(1<=n<=30,1<=w<=2∗109),表示零食的数量和背包的容量。
第二行 n n n个正整数 v [ i ] ( 0 < = v [ i ] < = 1 0 9 ) v[i](0 <= v[i] <= 10^9) v[i](0<=v[i]<=109),表示每袋零食的体积。
输出描述
输出一个正整数, 表示牛牛一共有多少种零食放法。
示例
输入:
3 10
1 2 4
输出:
8
N N N个零食就有 2 N 2^N 2N种防治方法,遍历这些放置方法,检查哪些方法是有效的,时间复杂度为 O ( N ∗ 2 N ) O(N*2^N) O(N∗2N),复杂度相当高,当然也没有被ac。
这里涉及到一个问题就是如何判断方法是否有效,以示例为例来说,3个零食就有8中方法,1~4的二进制表示为:
1: 001 ---- 第一个零食放下
2: 010 ---- 第二个零食放下
3: 011 ---- 第一、二个零食被放下
4: 100 ---- 第三个零食放下
可以看到二进制表示中的每位上的1就代表了第几个零食应该放下,因此我们只需要去判断放下这些零食是否会超出存储空间即可。
if __name__ == '__main__':
n, w = map(int, input().split())
v = list(map(int, input().split()))
v_new = []
for i in v:
if i <= w:
v_new.append(i)
n = len(v_new)
total_num = 1<<n
res = 0
for i in range(total_num):
left = w
j = 0
while j < n and left >= 0:
if i & (1<<j):
left -= v[j]
j += 1
if left >= 0: res += 1
print(res)
使用中途相遇法的思想,将 N N N份零食一分两半,枚举第一份的所有取法,用一个体积数组记录体积和,再枚举第二份的所有取法,从体积数组中找到大于等于
第二份体积的第一个位置,就是所有的取法。
if __name__ == '__main__':
import bisect
n, w = map(int, input().split())
v = list(map(int, input().split()))
v_new = []
for i in v:
if i <= w:
v_new.append(i)
n = len(v_new)
up = n // 2
down = n - n//2
space = []
res = 0
total_up = 1<<up
for i in range(total_up):
left = w
j = 0
while j < up and left >= 0:
if i & (1<<j):
left -= v[j]
j += 1
if left >= 0:
space.append(w-left)
space.sort()
total_down = 1<<down
for i in range(total_down):
left = w
j = 0
while j < down and left >= 0:
if i & (1<<j):
left -= v[j+up]
j += 1
if left >= 0:
# res = bisect.bisect_right(space, left) - space[0]
res += bisect.bisect_right(space, left)
print(res)
这里需要说明一下为什么res += bisect.bisect_right(space, left):
space数组中存储的是第一份数组的存储空间,而第二份数组的存储空间在space中找到相应的位置就代表着有多少种取法。
def dfs(idx, n, visited, count, left, v):
if idx == n:
return
for i in range(idx, n):
if not visited[i] and left >= v[i]: #当前点没有被访问且存储空间能够装下
visited[i] = True
count += 1
left -= v[i]
#继续往后搜索看是否有符合条件的点
dfs(i+1, n, visited, count, left, v)
visited[i] = False #释放当前的点
left += v[i] #释放存储空间
if __name__ == '__main__':
n, w = map(int, input().split())
v = list(map(int, input().split()))
if sum(v) <= w:
print(1<<n)
else:
visited = [False]*n
count = 1
left = w
dfs(0, n, visited, count, left, v)
print(count)
其实这里有个坑,就是count=[1],如果count=1就会出错,至今我也没弄清楚这个bug的缘由。
此外,这里也给出dfs的一个伪代码:
/**
* DFS核心伪代码
* 前置条件是visit数组全部设置成false
* @param n 当前开始搜索的节点
* @param d 当前到达的深度,也即是路径长度
* @return 是否有解
*/
bool DFS(Node n, int d){
if (d == 4){//路径长度为返回true,表示此次搜索有解
return true;
}
for (Node nextNode in n){//遍历跟节点n相邻的节点nextNode,
if (!visit[nextNode]){//未访问过的节点才能继续搜索
//例如搜索到V1了,那么V1要设置成已访问
visit[nextNode] = true;
//接下来要从V1开始继续访问了,路径长度当然要加
if (DFS(nextNode, d+1)){//如果搜索出有解
//例如到了V6,找到解了,你必须一层一层递归的告诉上层已经找到解
return true;
}
//重新设置成未访问,因为它有可能出现在下一次搜索的别的路径中
visit[nextNode] = false;
}
//到这里,发现本次搜索还没找到解,那就要从当前节点的下一个节点开始搜索。
}
return false;//本次搜索无解