CSP202209.B何以包邮?

今天我们来看CSP202209.B何以包邮?这道题目

解题思路

问题分析

本题是一个典型的0-1背包问题变形。我们需要在 n 本书中选择若干本,使得总价格不小于 x,并且尽可能接近 x。

算法选择

  1. 暴力枚举:对于 n≤30,直接枚举所有子集的时间复杂度为 O(2^n),会超时。
  2. 折半枚举:将书分成两半,分别枚举,时间复杂度 O(2^(n/2)),可以接受。
  3. 动态规划:使用 0-1 背包思想,时间复杂度 O(n×sum),其中 sum 为总价格。

由于每本书价格 ai≤10^4,n≤30,所以总价格 sum≤3×10^5,动态规划完全可行。

动态规划思路

状态定义:

  • 设 dp[j] 表示能否通过选择若干本书,使得总价格恰好为 j。

状态转移:

  • 初始状态:dp[0] = true,其余为 false
  • 对于每本书 a[i],从大到小遍历 j,更新 dp[j] = dp[j] || dp[j - a[i]]

答案查找:

  • 从 x 开始向上遍历,找到第一个 dp[j] == true 的 j,即为满足条件的最小花费

复杂度分析

  • 时间复杂度:O(n × sum),其中 sum 为所有书价格总和
  • 空间复杂度:O(sum)

C++ 代码实现

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, x;
    cin >> n >> x;
    
    vector<int> a(n);
    int sum = 0;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        sum += a[i];
    }

    // dp[j] 表示能否凑出总价格恰好为 j
    vector<bool> dp(sum + 1, false);
    dp[0] = true;

    // 0-1背包:每本书只能选择一次
    for (int i = 0; i < n; i++) {
        // 从大到小遍历,避免重复选择同一本书
        for (int j = sum; j >= a[i]; j--) {
            if (dp[j - a[i]]) {
                dp[j] = true;
            }
        }
    }

    // 从 x 开始向上找第一个能凑出的价格
    for (int j = x; j <= sum; j++) {
        if (dp[j]) {
            cout << j << "\n";
            break;
        }
    }

    return 0;
}

代码详解

1. 输入处理

cpp 复制代码
int n, x;
cin >> n >> x;
vector<int> a(n);
int sum = 0;
for (int i = 0; i < n; i++) {
    cin >> a[i];
    sum += a[i];  // 计算总价格,用于确定 dp 数组大小
}

2. 动态规划数组初始化

cpp 复制代码
vector<bool> dp(sum + 1, false);
dp[0] = true;  // 不选择任何书时,总价格为 0

3. 0-1背包核心代码

cpp 复制代码
for (int i = 0; i < n; i++) {
    for (int j = sum; j >= a[i]; j--) {
        if (dp[j - a[i]]) {
            dp[j] = true;  // 如果 j-a[i] 可以凑出,那么 j 也可以凑出
        }
    }
}

注意:内层循环必须从大到小遍历,这样才能保证每本书最多被选择一次(0-1背包特性)。

4. 查找答案

cpp 复制代码
for (int j = x; j <= sum; j++) {
    if (dp[j]) {
        cout << j << "\n";
        break;  // 找到第一个满足条件的就输出
    }
}

示例运行

输入示例

复制代码
5 15
5
7
3
8
2

运行过程

  • 总价格 sum = 25
  • dp 数组大小为 26
  • 经过动态规划后,dp15 = true(可以选择 5+7+3 = 15)
  • 从 15 开始查找,找到 dp15 = true
  • 输出:15

注意事项

  1. 数组大小:dp 数组大小需要设为 sum+1,因为可能的最大价格是 sum
  2. 遍历顺序:内层循环必须从大到小,避免一本书被重复选择
  3. 边界条件:题目保证 sum≥x,所以一定能找到答案
  4. 优化:可以使用 bitset 优化空间和时间,但对于本题数据范围,bool 数组已经足够

总结

本题是经典的 0-1 背包问题变体,通过动态规划可以高效求解。关键点在于:

  1. 正确理解 0-1 背包的状态转移
  2. 掌握从大到小遍历的技巧
  3. 学会从 x 开始查找满足条件的最小值

这种方法时间复杂度为 O(n×sum),在给定数据范围内可以高效运行。

相关推荐
言乐610 分钟前
Python实现语音审核模型
开发语言·python·django·virtualenv·pygame
朝朝辞暮i42 分钟前
C++ 第 21 课:struct —— 把一组相关数据打包在一起
开发语言·c++·算法
SHARK_pssm1 小时前
【C++——类和对象(下)】
开发语言·c++·经验分享·笔记
凯哥Java1 小时前
写代码怎么避免逻辑漏洞?
java·开发语言·人工智能·自动化
Escalating_xu1 小时前
【C 语言】操作符详解(下):逗号、下标、结构体、类型转换与表达式求值
c语言
Ai-_Man2 小时前
您您这可以把Dola的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。用AI导出鸭,答案是可以的
开发语言·前端·人工智能·小程序
Java后端的Ai之路2 小时前
Python进阶探索23 - Python中的Time与Datetime模块
开发语言·数据库·python·datetime·time
沫璃染墨2 小时前
《从零入门Linux系统篇(五十四):线程篇·七——互斥锁底层原理:从原子交换到线程竞争与锁实现》
linux·运维·服务器·开发语言·c++·驱动开发·系统架构
Java后端的Ai之路3 小时前
Python进阶探索24_应用案例_linux系统的监控
linux·开发语言·python·应用·探索
晓蛋3 小时前
C语言条件编译精讲
c语言·条件编译·宏定义·代码移植·预处理程序