这道题目来自XTUOJ(湘潭大学在线评测系统)的第1436题,题目名为"礼物"。从标题和描述来看,这是一个典型的背包问题变种,要求使用深度优先搜索(DFS)来解决。
背包问题在算法竞赛和实际工程中都非常常见,特别是在资源分配、投资组合优化等场景。01背包作为最基础的背包问题,要求从一组物品中选择若干放入容量有限的背包,使得总价值最大。而这道题的特殊之处在于要求使用DFS解法,这通常出现在教学场景中,帮助学生理解递归和回溯的思想。
提示:虽然动态规划是解决背包问题的更优方案,但DFS解法对于理解问题本质和训练递归思维非常有帮助。
假设我们有N件礼物,每件礼物有自己的重量w[i]和价值v[i]。给定一个最大承重W,我们需要选择若干礼物,使得总重量不超过W,且总价值最大。这就是经典的01背包问题。
使用DFS解法的核心思想是:对于每件礼物,我们有两种选择 - 拿或者不拿。通过递归地尝试所有可能的组合,找到满足重量限制的最大价值解。
典型的输入格式可能是:
code复制N W
w1 v1
w2 v2
...
wn vn
其中:
输出应为能获得的最大价值。
python复制def dfs(index, current_weight, current_value):
if index == n: # 所有物品都考虑过了
if current_weight <= W and current_value > max_value:
max_value = current_value
return
# 选择拿当前物品
if current_weight + w[index] <= W:
dfs(index + 1, current_weight + w[index], current_value + v[index])
# 选择不拿当前物品
dfs(index + 1, current_weight, current_value)
基本DFS的时间复杂度是O(2^N),当N较大时(如N>20)会非常慢。我们可以通过以下优化来提升效率:
可行性剪枝:在递归过程中,如果当前累计重量已经超过W,可以直接返回,不再继续搜索。
最优性剪枝:维护当前找到的最大价值,如果在某个分支中,即使拿走剩余所有物品也无法超过当前最大值,就可以提前终止该分支。
优化后的DFS实现:
python复制max_value = 0
def dfs(index, current_weight, current_value, remaining_value):
global max_value
if current_weight > W:
return
if current_value > max_value:
max_value = current_value
if index == n or current_value + remaining_value <= max_value:
return
# 拿当前物品
dfs(index + 1, current_weight + w[index], current_value + v[index], remaining_value - v[index])
# 不拿当前物品
dfs(index + 1, current_weight, current_value, remaining_value - v[index])
# 初始调用
total_value = sum(v)
dfs(0, 0, 0, total_value)
python复制def solve():
import sys
sys.setrecursionlimit(1000000)
n, W = map(int, sys.stdin.readline().split())
w = []
v = []
for _ in range(n):
wi, vi = map(int, sys.stdin.readline().split())
w.append(wi)
v.append(vi)
max_value = 0
total_value = sum(v)
def dfs(index, current_weight, current_value, remaining_value):
nonlocal max_value
if current_weight > W:
return
if current_value > max_value:
max_value = current_value
if index == n or current_value + remaining_value <= max_value:
return
# 拿当前物品
dfs(index + 1, current_weight + w[index], current_value + v[index], remaining_value - v[index])
# 不拿当前物品
dfs(index + 1, current_weight, current_value, remaining_value - v[index])
dfs(0, 0, 0, total_value)
print(max_value)
solve()
基本DFS的时间复杂度是O(2^N),经过剪枝优化后,实际运行时间会大大减少,但最坏情况下仍然是指数级的。
空间复杂度主要来自递归调用栈,最坏情况下是O(N)。
记忆化搜索:可以记录已经计算过的状态,避免重复计算。但对于01背包问题,DFS+记忆化实际上就相当于动态规划了。
迭代加深:可以尝试限制递归深度,逐步增加搜索深度。
启发式搜索:按照某种启发式规则(如价值密度)决定搜索顺序,可能更快找到较优解。
虽然题目要求使用DFS,但了解动态规划解法对于全面理解背包问题很有帮助。
python复制def knapsack():
dp = [0] * (W + 1)
for i in range(n):
for j in range(W, w[i] - 1, -1):
dp[j] = max(dp[j], dp[j - w[i]] + v[i])
return dp[W]
| 特性 | DFS解法 | DP解法 |
|---|---|---|
| 时间复杂度 | O(2^N)(优化后更好) | O(N*W) |
| 空间复杂度 | O(N) | O(W) |
| 适用场景 | N较小(≤30) | W较小(≤1e6) |
| 代码复杂度 | 中等 | 简单 |
| 教学价值 | 高(理解递归回溯) | 高(理解DP思想) |
01背包问题在实际中有很多应用场景:
常见的变种问题包括:
递归深度限制:Python默认递归深度约1000,对于N>1000的问题需要设置sys.setrecursionlimit
时间限制:DFS解法通常只适用于N≤30的问题,更大规模应该考虑DP或其他优化方法
空间优化:递归解法会使用栈空间,要注意不要超过内存限制
输入输出效率:在Python中,使用sys.stdin读取输入比input()更快
虽然这道题要求使用DFS,但在实际竞赛和工程中,我们需要根据问题规模选择合适的解法:
理解各种解法的适用场景和局限性,才能在实际问题中做出最佳选择。DFS解法虽然效率不高,但对于理解问题本质和训练递归思维非常有价值。