题解:洛谷 B4177 尽量接近
【题目来源】
洛谷:B4177 [BCSP-X 2024 6 月初中组] 尽量接近
【题目描述】
给出 \(N\) 个整数,要求从中选出若干个数,使得它们的和尽量接近整数 \(K\)。
【输入】
- 第一行两个正整数 \(N, K\)。
- 第二行 \(N\) 个数,表示给出的数字。
【输出】
共一行一个整数,表示最接近 \(K\) 的和。如果不唯一,输出较小的那个。
【输入样例】
4 12
5 6 9 4
【输出样例】
11
【核心思想】
-
问题分析:给定 \(N\) 个整数和一个目标值 \(K\),从中选出若干个数使它们的和尽量接近 \(K\)(差值最小,差值相同时取较小和)。这是一个01 背包的变形问题,核心在于用布尔 DP 记录所有可达的子集和,再从中筛选最接近 \(K\) 的值。
-
算法选择:
- 01 背包(布尔型):\(f[i][j]\) 表示考虑前 \(i\) 个数能否凑出和 \(j\)
- 答案提取:在所有可达的和中找到与 \(K\) 差值最小者,差值相同时取较小和
-
关键步骤:
- 初始化:读取 \(N\)、\(K\)、\(a[1..N]\),计算总和 \(sum = \sum_{i=1}^{N} a_i\)
- DP 递推(\(i\) 从 \(1\) 到 \(N\),\(j\) 从 \(0\) 到 \(sum\)):
- 不选 \(a_i\):\(f[i][j] = f[i-1][j]\)
- 选 \(a_i\)(若 \(j \geq a_i\)):\(f[i][j] \mathrel{|}= f[i-1][j-a_i]\)
- 边界:\(f[0][0] = true\)(考虑 \(0\) 个数时和为 \(0\) 可达)
- 答案提取:遍历 \(j\) 从 \(0\) 到 \(sum\),若 \(f[N][j] = true\):
- 计算 \(diff = |j - K|\)
- 若 \(diff < minn\):更新 \(minn = diff\),\(ans = j\)
- 若 \(diff = minn\) 且 \(j < ans\):更新 \(ans = j\)(取较小和)
- 输出答案 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times sum)\),\(sum\) 为所有数的总和
- 空间复杂度:\(O(N \times sum)\),布尔型 DP 数组(可滚动优化至 \(O(sum)\))
-
01 背包的核心思想:
- 子集和判定:布尔型 DP 将"能否凑出和 \(j\)"作为状态,通过或运算累积所有可能的选择组合
- 状态转移的完备性:每个数只有选或不选两种决策,\(f[i][j]\) 覆盖前 \(i\) 个数的所有子集情况
- 答案域遍历:DP 完成后不是直接输出 \(f[N][K]\),而是遍历所有可达和找最优,因为 \(K\) 本身可能不可达
- 平局处理:差值相同时取较小和,通过 \(j < ans\) 的条件确保
- 适用于子集和判定、最接近目标值、资源分配可行性类问题
【算法标签】
普及 #01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 55, K = 1000005; // N:最大整数个数, K:DP数组第二维上限
int n, k; // n:整数个数, k:目标和
int a[N]; // a[i]:第i个整数的值
bool f[N][K]; // f[i][j]:考虑前i个数,能否凑出和为j的子集
int main()
{
cin >> n >> k; // 读入整数个数和目标和
int sum = 0; // 所有数的总和
for (int i=1; i<=n; i++)
{
cin >> a[i]; // 读入每个整数
sum += a[i]; // 累加总和
}
f[0][0] = true; // 边界条件:考虑0个数时,和为0是可达的
for (int i=1; i<=n; i++) // 外层循环:枚举每个整数
for (int j=0; j<=sum; j++) // 内层循环:枚举可能的子集和
{
f[i][j] = f[i-1][j]; // 不选第i个数:继承前i-1个数的状态
if (j>=a[i]) // 如果当前和j大于等于a[i]
f[i][j] |= f[i-1][j-a[i]]; // 选第i个数:检查j-a[i]是否可达
}
int ans = 0; // 最接近k的子集和
int minn = 1e9; // 当前找到的最小差值
for (int j=0; j<=sum; j++) // 遍历所有可能的子集和
if (f[n][j]) // 如果和j是可达的
{
int diff = abs(j-k); // 计算与目标k的差值
if (diff<minn) // 如果差值更小,更新答案
{
minn = diff;
ans = j;
}
else if (diff==minn && j<ans) // 差值相同但和更小,取较小值
{
ans = j;
}
}
cout << ans << endl; // 输出最接近k的子集和
return 0;
}
【运行结果】
4 12
5 6 9 4
11
浙公网安备 33010602011771号