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),在给定数据范围内可以高效运行。

相关推荐
yanlaifan43 分钟前
C语言标准版本简单介绍
c语言
PHP实战开发录1 小时前
PHP脚本手动能跑定时任务却失败
开发语言·php·开发
郝学胜-神的一滴2 小时前
Effective Python 条款 10 :海象运算符_=
开发语言·python·程序人生·开源
wuyk5552 小时前
Python零基础入门第十四章:异常处理(try-except)
开发语言·python
wuyk5552 小时前
【Socket 进阶之路】第 3 章 TCP 三次握手 & 四次挥手深度剖析|连接建立、断开、状态机、TIME_WAIT 核心工程问题
服务器·开发语言·网络·物联网·网络协议·tcp/ip
彧azz8 小时前
图的存储结构详解:邻接矩阵的原理、实现与应用
开发语言·数据结构·学习·php
嵌入式学习菌8 小时前
Modbus‑RTU 数据类型分析
开发语言·单片机·bug
matlab代码9 小时前
基于matlab数字图像处理的指纹识别系统【源码68期】
开发语言·matlab
matlab代码9 小时前
基于matlab的水果识别系统(苹果香蕉菠萝梨桃子)【源码65期】
开发语言·matlab
wuyk55510 小时前
从零吃透 MQTT 通信|第 8 章 FreeRTOS 多任务架构下 MQTT 工程架构,任务拆分、队列解耦、临界区保护
c语言·开发语言·stm32·学习·架构