题目
小明有n个可选运动,每个运动有对应卡路里,想选出其中k个运动且卡路里和为t。k,t,n都是给定的。求出可行解数量
输入描述
第一行输入n t k
第二行输入 每个运动的卡路里 按照空格进行分割
备注
0<n<10
t>0,0<k<=n
每个运动量的卡路里>0
输出描述
求出可行解数量
示例1:
输入
4 3 2
1 1 2 3
输出
2
说明
可行解为2,选取{0,2},{1,2}两种方式。
思路一(暴力解法)
可以使用回溯法来枚举所有可能的k个运动的组合,并统计满足条件的组合数量。
算法过程
-
输入处理:读取输入的n、t、k和卡路里数组。
-
回溯搜索:从第一个运动开始,尝试选择或不选择当前运动,递归搜索所有可能的组合。
-
剪枝条件:
-
如果已选运动数量超过k,终止搜索。
-
如果剩余运动不足以达到k个,终止搜索。
-
如果当前卡路里之和超过t,终止搜索。
-
-
终止条件:当已选运动数量为k且卡路里之和等于t时,计数加1。
-
该算法的时间复杂度为 O (C (n, k)),其中 C (n, k) 表示组合数。由于 n 的最大值为 10,组合数的最大值为 252,因此算法在给定约束条件下是高效的。
参考代码
function solution() {
const [n, t, k] = readline().split(" ").map(Number);
const calories = readline().split(" ").map(Number);
let count = 0;
function backtrack(start, currentSum, currentSize) {
if (currentSize === k) {
if (currentSum === t) {
count++;
}
return;
}
if (currentSize > k || currentSum > t) {
return;
}
for (let i = start; i < n; i++) {
backtrack(i + 1, currentSum + calories[i], currentSize + 1);
}
}
backtrack(0, 0, 0);
console.log(count);
}
const cases = [
`4 3 2
1 1 2 3`,
];
let caseIndex = 0;
let lineIndex = 0;
const readline = (function () {
let lines = [];
return function () {
if (lineIndex === 0) {
lines = cases[caseIndex]
.trim()
.split("\n")
.map((line) => line.trim());
}
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
});
思路二(动态规划)
题意可以简化为从 n 个物品中选取 k 个恰好装满容量为 t 的背包的方案总数。这是一个典型的背包问题,属于恰好装满的 0-1 背包计数问题。其特点是:
- 每个物品只能选或不选(0-1 性质)。
- 必须恰好装满容量为 t 的背包。
- 目标是计算满足条件的方案总数,而非最大价值。
算法过程
-
状态定义:
dp[j][s]表示选择j个运动,卡路里总和为s的组合数目。 -
初始化:
dp[0][0] = 1,表示不选任何运动且总和为 0 的情况只有一种。 -
状态转移:
-
对于每个运动,逆序遍历
j从k到1 -
对于每个
j,逆序遍历s从t到当前运动的卡路里值 -
更新
dp[j][s]的值,加上dp[j-1][s-cal](即选择当前运动的情况)
-
-
结果:最终结果存储在
dp[k][t]中,表示选择k个运动且总和为t的组合数目。
复杂度分析
-
时间复杂度:仍然是 O (n * k * t),与三维数组版本相同
-
空间复杂度:优化为 O (k * t)
参考代码
function solution() {
const [_, t, k] = readline().split(" ").map(Number);
const calories = readline().split(" ").map(Number);
// 创建二维DP数组并初始化为0
const dp = Array.from({ length: k+1 }, () => Array(t+1).fill(0));
// 初始化:不选任何物品,重量为0,方案数为1
dp[0][0] = 1;
// 处理每个物品
for (const cal of calories) {
// 逆序遍历j和s,避免覆盖需要的旧状态
for (let j = k; j >= 1; j--) {
for (let s = t; s >= cal; s--) {
// 状态转移:选当前物品
dp[j][s] += dp[j-1][s - cal];
}
}
}
// 结果:选k个物品,总重量为t的方案数
console.log(dp[k][t]);
}
const cases = [
`4 3 2
1 1 2 3`,
];
let caseIndex = 0;
let lineIndex = 0;
const readline = (function () {
let lines = [];
return function () {
if (lineIndex === 0) {
lines = cases[caseIndex]
.trim()
.split("\n")
.map((line) => line.trim());
}
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
});

1732

被折叠的 条评论
为什么被折叠?



