一、题目描述
为了提升软件编程能力,小王制定了刷题计划,他选择了题库中的n道题,编号从0到n-1,并计划在m天内按照题目编号顺序完成所有的题目(注意,小王不能用多天完成同一题)。
在小王刷题计划中,小王需要用time[i]的时间完成编号i的题目。
此外,小王还可以查看答案,以减少该题的做题时间。为了真正达到刷题效果,小王每天最多直接看一次答案。
我们定义m天内做题时间最多的一天耗时为T(直接看答案的题目不计入做题总时间)。
请你帮小王求出最小的T是多少。
二、输入描述
第一行输入为time,time[i]是时间完成编号i的题目
第二行输入为m,m表示几天内完成所有题目,1 ≤ m ≤ 180
三、输出描述
最小耗时整数T
四、测试用例
测试用例1:
1、输入
999,999,999
4
2、输出
0
3、说明
测试用例2:
1、输入
1,2,2,3,5,4,6,7,8
5
2、输出
4
3、说明
五、解题思路
1、题目分析
小王共选择了 n 道题,按顺序完成,每天必须完成连续的一段题目。每天允许“看答案”一次,即可以将当天某一道题的时间变为0,从而该天的耗时为该天所有题目的时间和减去当天所选“看答案”的题目的时间(最优策略是选择耗时最大的题目来“看答案”,从而最大化时间减免)。记每一天的实际耗时为 cost=(当天题目总时间)−(当天最大题目时间)。
目标是将所有题目分成不超过 m 段,使得各天耗时的最大值 T 尽可能小。
2、解题思路
决策问题转换:对于给定的 T,判断是否存在一种分割方法,使得每一天的实际耗时不超过 T。
动态规划(DP)检查可行性:设 dp[i] 表示前 i 道题可以用最少多少天完成,使得每一段(连续一部分题目)的耗时(总和减去该段最大值)均不超过 T。初始 dp[0] = 0;从 i 出发,尝试延伸连续段 [i, j],维护累计和 sum 和当前最大值 max,每次计算 cost = sum – max。如果 cost ≤ T,则可以更新 dp[j+1] = min(dp[j+1], dp[i] + 1);最后若 dp[n] ≤ m,则 T 可行。
二分答案:由于 T 的范围在 0 到 sum(time) 内,我们采用二分搜索最小的 T,使得上述DP可行。
通过动态规划遍历所有可能的分割区间,并更新所需的最小天数。通过二分查找在答案范围内二分查找最小可行 T。
选择这两种方法主要原因是题目具有分割问题的特点,使用动态规划可准确判断某个 T 是否可行,而二分搜索可以高效地缩小答案范围。
六、Java算法源码
public class OdTest {
// 判断在给定T下是否能在不超过m天内完成所有题目
public static boolean canPartition(long T, int[] time, int m) {
int n = time.length;
int[] dp = new int[n + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0; // 0道题需要0天
// 遍历每个起点i
for (int i = 0; i < n; i++) {
if (dp[i] == Integer.MAX_VALUE) continue;
long sum = 0;
int maxVal = 0;
// 尝试从i开始延伸到j
for (int j = i; j < n; j++) {
sum += time[j];
maxVal = Math.max(maxVal, time[j]);
long cost = sum - maxVal; // 当前区间的实际耗时
if (cost <= T) {
dp[j + 1] = Math.min(dp[j + 1], dp[i] + 1);
}
}
}
return dp[n] <= m;
}
// 利用二分查找求最小的T
public static long findMinT(int[] time, int m) {
long low = 0;
long high = 0;
for (int t : time) {
high += t;
}
long ans = high;
while (low <= high) {
long mid = low + (high - low) / 2;
if (canPartition(mid, time, m)) {
ans = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
return ans;
}
// 将输入字符串转换为整数数组,输入格式为 "a,b,c"
public static int[] parseTime(String s) {
String[] parts = s.split(",");
int[] arr = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
arr[i] = Integer.parseInt(parts[i].trim());
}
return arr;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 输入 time 数组
String timeInput = scanner.nextLine();
int[] timeArr = parseTime(timeInput);
// 输入 m 值
int m = scanner.nextInt();
// 计算并输出结果
long result = findMinT(timeArr, m);
System.out.println(result);
scanner.close();
}
}
七、Python算法源码
# 判断是否可以分配任务,返回是否满足时间限制T
def can_partition(T, time, m):
n = len(time)
dp = [float('inf')] * (n + 1) # dp[i]表示前i个任务所需的最少分配组数
dp[0] = 0 # 初始状态,0个任务需要0组
# 遍历每个任务
for i in range(n):
if dp[i] == float('inf'):
continue # 如果当前任务不可达,跳过
s = 0
max_val = 0
# 遍历从任务i到任务n之间的所有任务
for j in range(i, n):
s += time[j] # 累加任务时间
max_val = max(max_val, time[j]) # 获取当前任务的最大时间
cost = s - max_val # 计算当前任务的实际花费时间
if cost <= T: # 如果花费时间小于等于T,更新dp数组
dp[j + 1] = min(dp[j + 1], dp[i] + 1)
return dp[n] <= m # 判断最终是否能分配为m组以内
# 查找最小的T,使得任务可以分配成m组
def find_min_T(time, m):
low = 0
high = sum(time) # 任务的总时间
ans = high
# 二分法查找最小的T
while low <= high:
mid = low + (high - low) // 2
if can_partition(mid, time, m): # 如果可以分配,尝试更小的T
ans = mid
high = mid - 1
else: # 否则增加T
low = mid + 1
return ans # 返回最小的T
# 解析输入的任务时间字符串
time_input = input()
time = list(map(int, time_input.split(','))) # 转换为列表
m = int(input()) # 输入组数m
result = find_min_T(time, m) # 调用函数查找最小T
print(result) # 输出结果
八、JavaScript算法源码
// 判断是否可以分配任务,返回是否满足时间限制T
function canPartition(T, time, m) {
const n = time.length;
const dp = new Array(n + 1).fill(Infinity); // dp[i]表示前i个任务所需的最少分配组数
dp[0] = 0; // 初始状态,0个任务需要0组
// 遍历每个任务
for (let i = 0; i < n; i++) {
if (dp[i] === Infinity) continue; // 如果当前任务不可达,跳过
let s = 0;
let maxVal = 0;
// 遍历从任务i到任务n之间的所有任务
for (let j = i; j < n; j++) {
s += time[j]; // 累加任务时间
maxVal = Math.max(maxVal, time[j]); // 获取当前任务的最大时间
const cost = s - maxVal; // 计算当前任务的实际花费时间
if (cost <= T) { // 如果花费时间小于等于T,更新dp数组
dp[j + 1] = Math.min(dp[j + 1], dp[i] + 1);
}
}
}
return dp[n] <= m; // 判断最终是否能分配为m组以内
}
// 查找最小的T,使得任务可以分配成m组
function findMinT(time, m) {
let low = 0;
let high = time.reduce((a, b) => a + b, 0); // 任务的总时间
let ans = high;
// 二分法查找最小的T
while (low <= high) {
const mid = low + Math.floor((high - low) / 2);
if (canPartition(mid, time, m)) { // 如果可以分配,尝试更小的T
ans = mid;
high = mid - 1;
} else { // 否则增加T
low = mid + 1;
}
}
return ans; // 返回最小的T
}
// 解析输入的任务时间字符串
const timeInput = prompt();
const time = timeInput.split(',').map(Number); // 转换为数组
const m = parseInt(prompt(), 10); // 输入组数m
const result = findMinT(time, m); // 调用函数查找最小T
console.log(result); // 输出结果
九、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
// 判断是否可以分配任务,返回是否满足时间限制T
int canPartition(long T, int *time, int n, int m) {
int *dp = (int *)malloc((n + 1) * sizeof(int));
for (int i = 0; i <= n; i++) {
dp[i] = INT_MAX; // 初始化为最大值
}
dp[0] = 0; // 初始状态,0个任务需要0组
// 遍历每个任务
for (int i = 0; i < n; i++) {
if (dp[i] == INT_MAX) continue; // 如果当前任务不可达,跳过
long sum = 0;
int maxVal = 0;
// 遍历从任务i到任务n之间的所有任务
for (int j = i; j < n; j++) {
sum += time[j]; // 累加任务时间
if (time[j] > maxVal)
maxVal = time[j]; // 获取当前任务的最大时间
long cost = sum - maxVal; // 计算当前任务的实际花费时间
if (cost <= T && dp[i] + 1 < dp[j + 1]) {
dp[j + 1] = dp[i] + 1; // 更新dp数组
}
}
}
int feasible = (dp[n] <= m); // 判断最终是否能分配为m组以内
free(dp); // 释放内存
return feasible;
}
// 查找最小的T,使得任务可以分配成m组
long findMinT(int *time, int n, int m) {
long low = 0;
long high = 0;
for (int i = 0; i < n; i++) {
high += time[i]; // 任务的总时间
}
long ans = high;
// 二分法查找最小的T
while (low <= high) {
long mid = low + (high - low) / 2;
if (canPartition(mid, time, n, m)) { // 如果可以分配,尝试更小的T
ans = mid;
high = mid - 1;
} else { // 否则增加T
low = mid + 1;
}
}
return ans; // 返回最小的T
}
int main() {
int n, m;
scanf("%d", &n); // 输入任务数量
int *time = (int *)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
scanf("%d", &time[i]); // 输入每个任务的时间
}
scanf("%d", &m); // 输入组数m
long result = findMinT(time, n, m); // 调用函数查找最小T
printf("%ld\n", result); // 输出结果
free(time); // 释放内存
return 0;
}
十、C++算法源码
#include <iostream>
#include <vector>
#include <climits>
#include <sstream>
using namespace std;
// 判断是否可以分配任务,返回是否满足时间限制T
bool canPartition(long T, const vector<int>& time, int m) {
int n = time.size();
vector<int> dp(n + 1, INT_MAX); // dp[i]表示前i个任务所需的最少分配组数
dp[0] = 0; // 初始状态,0个任务需要0组
// 遍历每个任务
for (int i = 0; i < n; i++) {
if (dp[i] == INT_MAX) continue; // 如果当前任务不可达,跳过
long sum = 0;
int maxVal = 0;
// 遍历从任务i到任务n之间的所有任务
for (int j = i; j < n; j++) {
sum += time[j]; // 累加任务时间
maxVal = max(maxVal, time[j]); // 获取当前任务的最大时间
long cost = sum - maxVal; // 计算当前任务的实际花费时间
if (cost <= T) { // 如果花费时间小于等于T,更新dp数组
dp[j + 1] = min(dp[j + 1], dp[i] + 1);
}
}
}
return dp[n] <= m; // 判断最终是否能分配为m组以内
}
// 查找最小的T,使得任务可以分配成m组
long findMinT(const vector<int>& time, int m) {
long low = 0;
long high = 0;
for (int t : time) {
high += t; // 任务的总时间
}
long ans = high;
// 二分法查找最小的T
while (low <= high) {
long mid = low + (high - low) / 2;
if (canPartition(mid, time, m)) { // 如果可以分配,尝试更小的T
ans = mid;
high = mid - 1;
} else { // 否则增加T
low = mid + 1;
}
}
return ans; // 返回最小的T
}
// 主函数
int main() {
string line;
getline(cin, line); // 读取输入
stringstream ss(line);
vector<int> time;
string temp;
while (getline(ss, temp, ',')) {
time.push_back(stoi(temp)); // 转换为整数数组
}
int m;
cin >> m; // 输入组数m
long result = findMinT(time, m); // 调用函数查找最小T
cout << result << endl; // 输出结果
return 0;
}
716

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



