我的Data Structures and Algorithms代码仓:https://github.com/617076674/Data-Structures-and-Algorithms
原题链接:https://pintia.cn/problem-sets/16/problems/690
题目描述:
知识点:回溯
思路:回溯法寻求最优解题顺序
一开始我以为是贪心算法,我的贪心策略是:每次优先做所需时间最少的那道题。
无法通过测试点2和测试点3,仔细一想,这样的贪心策略是错误的。假设有两道题A和B,假设先解A再解B所需的时间分别是t1和t2,先解B再解A所需的时间分别是t3和t4,假设t1 + t2 == t3 + t4。如果t1 < t3那么,按我的策略我一定是先解A再解B,但事实上先解B再解A所得的时间也是一样的,我们不应该根据t1和t3的大小来判断先解哪一题,而应该根据A和B的编号来判断。也就是说,光知道t1和t3的值,我们是无法判断先解哪一题的,因为t2和t4的值会影响我们之前的判断。所以本题不适合贪心算法。
考虑到本题的数据规模都特别小,考虑使用回溯算法遍历所有可能的情况。
回溯函数传入3个变量,int型变量currentTime代表当前时间,int型变量penaltyTime代表debug次数,int型变量finalTime代表当前方案的解决时间。
每进入一次回溯函数,都需要根据当前路径的解题数量以及当前方案的解决时间来判断是否需要更新最优路径信息以及最小时间信息。
对[0, N)中的节点,递归调用回溯函数的条件是:
(1)当前节点i未被访问。
(2)当前节点第一次提交的时间在考试时间内。
(3)AC时间在考试时间内。
注意变量标记节点是否被访问的数组solved[]和当前路径的手动回溯过程。
对单个测试用例而言,时间复杂度是O(N!),空间复杂度是O(N)。
C++代码:
#include<iostream>
#include<vector>
using namespace std;
int H, N, t0, totalMinutes, totalTime, INF = 1000000000;
char names[9][21];
int t[9], d[9];
bool solved[9];
vector<int> tempSolvedNumber, solvedNumber; //分别存储dfs过程的当前路径和最优路径
int minTime; //记录最小时间
int calculateFixTime(int currentTime); //计算当前时刻对应的debug次数
void dfs(int currentTime, int penaltyTime, int finalTime); //当前时间currentTime,被拒绝次数penaltyTime,当前路径的花费时间finalTime
int main() {
while(true) {
scanf("%d", &H); //考试总小时数
if(H < 0) {
break;
}
scanf("%d %d", &N, &t0);
for(int i = 0; i < N; i++) {
scanf("%s %d %d", names[i], &t[i], &d[i]);
}
totalMinutes = H * 60; //考试总分钟数
minTime = INF;
solvedNumber.clear();
fill(solved, solved + N, false); //所有问题都没有被解决
dfs(t0, 0, 0); //初始时刻为t0,被拒绝次数为0次
printf("Total Time = %d\n", minTime);
for(int i = 0; i < solvedNumber.size(); i++) {
printf("%s\n", names[solvedNumber[i]]);
}
}
return 0;
}
int calculateFixTime(int currentTime) {
if(currentTime <= 60) {
return 0;
} else if(currentTime > 60 && currentTime <= 120) {
return 1;
} else if(currentTime > 120 && currentTime <= 180) {
return 2;
} else if(currentTime > 180 && currentTime <= 240) {
return 3;
} else if(currentTime > 240 && currentTime <= 300) {
return 4;
}
}
void dfs(int currentTime, int penaltyTime, int finalTime) {
//如果当前路径的解题数量比历史最优解题数量要多
if((tempSolvedNumber.size() > solvedNumber.size()) ||
//或者当前路径的解题数量和历史最优解题数量相同,但是总时间花费比最小时间还要少
(tempSolvedNumber.size() == solvedNumber.size() && finalTime + penaltyTime * 20 < minTime)) {
solvedNumber = tempSolvedNumber; //更新历史最优解题数量
minTime = finalTime + penaltyTime * 20; //更新解题的最少时间
}
for(int i = 0; i < N; i++) {
if(!solved[i]) { //如果当前节点未被访问
int firstSubmit = currentTime + t[i]; //第一次提交的时间
if(firstSubmit <= totalMinutes) { //第一次提交的时间需要在考试时间内
int fixTime = calculateFixTime(firstSubmit);
if(firstSubmit + fixTime * d[i] <= totalMinutes) { //AC时间需要在考试时间内
solved[i] = true;
tempSolvedNumber.push_back(i);
dfs(firstSubmit + fixTime * d[i], penaltyTime + fixTime, finalTime + firstSubmit + fixTime * d[i]);
solved[i] = false; //变量的手动回溯
tempSolvedNumber.pop_back(); //变量的手动回溯
}
}
}
}
}
C++解题报告: