题目
临近期末考试的时间,就是莘莘学子开始担心自己学分的时候。
怠惰的小明这学期过得非常慵懒,为了让小明不挂科,辅导员规定小明的各个科目平时分的总和必须大于等于r。
三天打鱼两天晒网的小明的平时分不可能这么高,所以不得不补交一些作业。
小明这学期有n门课,对于第i门课,小明原有的平时分为ai,任课老师规定,小明每补交bi次作业,老师给他的第i 门课的平时分加1分。第i门课的平时分上限为ci,小明的分数达到上限后不能再继续加分。
爱偷懒的小明想问问你,最少他要补交几次作业才可以使得他的平时分总和大于等于r。
输入描述:第一行包含两个整数n, r,(1 <= n <= 100, r <= 1000)表示小明的学科数以及老师规定的平时分总和。接下来n行,每行包含3个整数 ai, bi ,ci(1 <= ai <= ci <= 1000, 1 <= bi <= 1000),分别表示小明原有的平时分,第i门课加一分所需要补交作业的次数以及第i门课的平时分上限,数据保证 ∑ci 大于 r
输出描述:最少需要补交作业的次数
输入:
2 30
10 3 40
10 2 40
输出:20
小明补交第2门课程20次作业,课程的平时分总和变为20 + 10 = 30,达到了老师的要求 。
分析
1.因为该题涉及多个变量,且这些变量之间存在一定的关系,且这些变量都属于同一个对象的属性,所以采用封装的思想,将这些变量,都定义在一个类中。实例化类时,对象自然有这些属性。
2.因为这些变量之间存在一定的关系,根据题意,进行升序排列,所以让该类继承Comparable,并重写compareTo方法。
3.在主函数里面循环生成类对象,并加入list,通过list,取出元素,依次进行补交作业次数的判断。同时也要用一个变量存储当前平时分的总和,为后面的计算做铺垫。
4.因为每一门课程平时分有上限,所有应考虑清楚,如何进行计算。要满足不超过上限,又要找到最少的补交次数,那么就可以贪心做:
- 如果所需要的分数<=当前该科最大分数-已有的分数,即该科可以补交的最大分数,说明可以只用补交这一科就能过,不需要继续补交了。
- 如果所需要的分数>当前该科最大分数-已有的分数,就将该科能补交的最大次数求出来,用sum累加;同时所需分数减对应的分值。继续找下一科。
细节
- 我用的list存储Work类的对象,尽管我已经重写了compareTo(),但是那只是定义了一个排序规则,并没有排好序,用list存储之后,还得再调用Collections.sort()方法进行排序,而且后面还得循环遍历list才能取出对象,会增大时间复杂度,所以代码改进,可以使用PriorityQueue;
- 所需要的分数,应该是额外的补加,我一开始想成了:“所需要的分数+已有的分数<=当前该科最大分数”,但这道题求的是补的~补的分数!然而我初始化的时候定义成
int stillNeed = r - cur;
那么相当于,已经减去了“已有的分数”,现在又重新加上,能解决什么问题??
我们要求的是“最少他要补交几次作业才可以使得他的平时分总和大于等于r。”注意是补的,补的意思就是你还得额外加入。
3. 一门课最多只能加到maxScore,加满了,就不能再修了,所以要保证一科一个最大补分,完了就得下一科,不要在一科上面循环处理。
代码
我第一次写的:
public class MainDSelf {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();// 学科数
int r = input.nextInt();// 规定分数
List<HomeWork> list = new ArrayList<>();
int cur = 0;// 记录当前平时分总和
for (int i = 0; i < n; i++) {
int preScore = input.nextInt();
int need = input.nextInt();
int maxScore = input.nextInt();
HomeWork work = new HomeWork(preScore, need, maxScore);
list.add(work);
cur += work.preScore;
}
Collections.sort(list);
int sum = 0;// 提交作业的最小总次数
int stillNeed = r - cur;
for (int i = 0; i < list.size(); i++) {
HomeWork obj = list.get(i);
while (stillNeed > 0) {
if (stillNeed <= obj.maxScore - obj.preScore) {
sum += stillNeed * obj.need;
stillNeed = 0;
} else {
int temp = obj.maxScore - obj.preScore;
sum += temp * obj.need;
stillNeed -= temp;
}
break;//这样做不好
}
}
System.out.println(sum);
}
}
class HomeWork implements Comparable<HomeWork> {
int preScore;// 已有的平时分
int need;// 加1分需要补交的作用
int maxScore;// 该科平时分最大上线
public HomeWork(int preScore, int need, int maxScore) {
this.preScore = preScore;
this.need = need;
this.maxScore = maxScore;
}
@Override
public int compareTo(HomeWork o) {
// TODO Auto-generated method stub
return this.need - o.need;
}
}
代码改进(看这个,理解清楚这个就好了)
public class MainD {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n = input.nextInt();// 学科数
int r = input.nextInt();// 规定平时分总和
int cur = 0;
PriorityQueue<Work> queue = new PriorityQueue<>();
for (int i = 0; i < n; i++) {
int preScore = input.nextInt();
int cost = input.nextInt();
int maxScore = input.nextInt();
Work work = new Work(preScore, cost, maxScore);
cur += preScore;// 记录所有科目的现有学分
queue.add(work);// 小顶堆
}
r = r - cur;// 看还需要多少学分
int ans = 0;
while (!queue.isEmpty() && r > 0) {
Work temp = queue.poll();
if (r <= temp.maxScore - temp.preScore) {
ans += r * temp.cost;
r = 0;
} else {
int t = temp.maxScore - temp.preScore;
ans += t * temp.cost;
r = r - t;
}
}
System.out.println(ans);
}
}
class Work implements Comparable<Work> {
int preScore;// 之前的成绩
int cost;// 提交的次数,才能够1分
int maxScore;// 最大的成绩
public Work(int preScore, int cost, int maxScore) {
this.preScore = preScore;
this.cost = cost;
this.maxScore = maxScore;
}
@Override
public int compareTo(Work o) {
if (this.cost != o.cost) {
return this.cost - o.cost;
}
return (o.maxScore - o.preScore) - (this.maxScore - this.preScore);
}
}
个人觉得:
compareTo()就下面这个就可以了。
@Override
public int compareTo(HomeWork o) {
// TODO Auto-generated method stub
return this.need - o.need;
}
两个代码都能过~~
反思
第一个动手操作这种类型的题,以后得多练习!!
end.