题目详情
背景
作为参加首届全省统一中考的学生,小林同学的压力非常大。每天老师都会布置很多批作业,每批作业具有相同的优先值(不同批的任务的优先值不一样),由若干个不同的任务组成,每个任务都有唯一的编号,小林同学会把每批作业先按照编号顺序整理好,然后按照这批作业的优先值,放入活页夹里。接下来,她将按照任务在活页夹的顺序依次把作业完成。
但近期情况有改变了:妈妈给她生了一个弟弟,所以她要时不时帮妈妈照看一下他。令她欣慰的是:可爱的弟弟非常乖,每次吵闹的时间和程度都是可以预期的。因此她可以确定当前能用于完成作业的最长时间,每项任务的可持续时间(最短时间和最长时间,其中设置最短时间是因为连续的学习时间非常宝贵,所以在预期弟弟会安静较长时间时,她要把时间放在那些工作量较大的任务上),然后按符合条件的任务在活页夹中的顺序来完成。
林同学非常乐观,当她整理完一批作业,或者结束一次工作时,都会找出该批作业的第三个任务或者活页夹中的第三个任务(因为3是她的幸运数字)看一下,如果任务数少于3个,她会愉快地说一声"OK"。
现在要输出林同学看到的任务和她说的"OK"。
输入时,每次输入一行:
(1)如果该行的第一个数字是正数N时,说明新来了一批任务,这批任务由N个任务构成。第二个数字是该批任务的优先值,为不超过10000的正整数,从第三个数字开始的2*N个数字是顺序的N个任务的信息,前面一个是任务号(不超过10000的正整数),后一个是任务的持续时间(不超过200的正整数)。(每批任务在输入时,已经按照编号排好序了,因此不需要考虑按顺序号的从小到大排序过程,只好按任务来到的顺序简单整理好,放进一个小链表就可以了。)。然后再将这批任务根据优先值插入活页夹(大链表)。
(2)如果该行的第一个数字是负数,则说明现在可以做作业了,接下来会输入三个数字,第一个数字是单项任务的最短持续时间mi,第二个数字是单项任务的最长持续时间ma,最后一个数字是本次工作的最长持续时间load.
(3)如果该行的第一个数字是0,则结束。
函数接口定义
Task* getBatch(int m); //接收一批任务,该批任务共有m项任务
Task* addBatch(Task *head, Task *h);//将h指向的小链表插入head指向的大链表,并返回新的大链表首指针。
void display(Task *head, int m);//输出第m项任务的信息,或者输出OK
Task* study(Task*head, int mi, int ma, int load);
//mi是本次学习过程中,所学习的各个单项任务的最短持续时间约束,ma是单项任务的最长持续时间约束, load是本次学习过程的最长持续时间
裁判测试程序样例
#include <iostream>
using namespace std;
struct Task{
int ID;//任务编号
int key;//任务优先级
int load;//任务持续时间
Task *next;
};
/* 请在这里填写答案 */
void del(Task* head){//删除链表
if(head==NULL) return;
del(head->next);
delete head;
}
const int K=3;//幸运数字
int main(){
int count, mi, ma, load;
Task *head=NULL, *h;
cin>>count;//输入行的第一个数字
while(count!=0){
if(count>0){ //新添加count个任务
h=getBatch(count);//构建链表,并将首指针返回给h
display(h, K);//显示小链表的第K个任务或输出OK
head=addBatch(head, h);//将小链表加入大链表,并将首指针返回
}else{
cin>>mi>>ma>>load;
head=study(head, mi, ma, load);
display(head,K);//显示大链表的第K个任务或输出OK
}
cin>>count;
}
del(head);
return 0;
}
输入样例
4 12 101 5 102 3 103 6 104 7
2 22 105 12 106 8
1 9 107 9
-1 5 10 25
0
输出样例
输出时先输出任务号,再输出任务的优先级,最后输出时间。如果没有可输出的任务,则输出“OK”。
103 12 6
OK
OK
105 22 12
说明
(1)输入:4 12 101 5 102 3 103 6 104 7后,
子链(即小链表)共有四项任务101,102,103和104,其中第三项任务是103, 所以输出它的编号,优先值和时间103 12 6
将子链加入活页夹(大链表)后,活页夹中也是四项任务
(2)输入:2 22 105 12 106 8后
子链中共有两项任务105和106,找不到第三项,所以输出OK。
加入活页夹中,此时活页夹中共有六项任务:101,102,103,104,105,106
(3)输入:1 9 107 9后
子链中共有一项任务,所以输出OK。
加入活页夹中,此时活页夹中共有七项任务,顺序为:107,101,102,103,104,105,106
(4)-1 5 12 25
则顺序完成活页夹中时间在5和12之间的任务,最多持续时间为20
第一项任务107,持续时间为9,符合要求,完成该项任务后,剩余时间为16
第二项任务101, 持续时间为5, 符合要求,完成该项任务后,剩余时间为11
第三项任务102,持续时间为3, 不符合最低持续时间为5的要求,直接忽略。
第四项任务103,持续时间为6,符合要求,完成该项任务后,剩余时间为5。
接下来的任务都没有足够时间完成,所以此时活页夹中剩余四项任务,为:102,104,105,106
此时输出活页夹的第三项任务信息:105 22 12
(5)输入0,结束
限制条件
代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
解读
题目要求解读
通过对裁判程序和端口定义的阅读,对相关端口解读如下:
(1)
Task* getBatch(int m); //接收一批任务,该批任务共有m项任务
该端口目的是从键盘读入,构建一个任务相关的链表,最后返回该链表的头结点指针。
注意,这个函数不接受每一排输入的第一个值,接受相应排输入的第二个值,因此要首先用变量储存相应排输入的第二个值,即优先度,并在生成节点时直接填入。
另外,若设置头部的空节点,可以优化代码,让一些特殊情况得到化简。但是在合并链表时,会增加代码的复杂度,因此可酌情选用有空节点的链表,作者选用了不带有头部空节点的链表结构。
(2)
Task* addBatch(Task *head, Task *h);//将h指向的小链表插入head指向的大链表,并返回新的大链表首指针。
该端口的目的是将指针h指向的链表按照需要插入到指针head指向的链表中,并返回插入后的链表头节点指针。
此处的按照需要即要根据优先度进行插入。此处可选用多种插入办法,考虑到链表结构二叉搜索复杂度高,因此采用了直接插入,即遍历head指向的链表并找到合适的位置插入。
(3)
void display(Task *head, int m);//输出第m项任务的信息,或者输出OK
该端口最简单,目的是访问链表head的第m个元素(其实m只有3这个取值,可以完全不使用参数m,不过为了严谨还是采用了未知m具体值的算法),若存在,则输出该节点的编号、优先级、占用时间;若不存在,则输出OK即可,无返回值。
(4)
Task* study(Task*head, int mi, int ma, int load);
//mi是本次学习过程中,所学习的各个单项任务的最短持续时间约束,ma是单项任务的最长持续时间约束, load是本次学习过程的最长持续时间
该端口目的是完成“学习”这个过程,无需考虑输出问题,只是单纯更改链表即可。其中mi是本次学习过程中,所学习的各个单项任务的最短持续时间约束,ma是单项任务的最长持续时间约束, load是本次学习过程的最长持续时间。遍历链表,依次选择持续时间在mi和ma之间的任务删除即可(记得比较剩余时间并在删除后修改之)。
思路解读
(1)
Task* getBatch(int m); //接收一批任务,该批任务共有m项任务
首先读入优先级,然后生成头节点,然后循环生成剩余节点。
注意:生成节点时,需要把next主动设置为NULL。
(2)
Task* addBatch(Task *head, Task *h);//将h指向的小链表插入head指向的大链表,并返回新的大链表首指针。
先考虑其中有链表为空的情况,此时直接返回另一节点。
再考虑h放入head之前的情况,此时将head设置为h最后一个元素的next,然后返回h。
最后依次遍历head,比较优先级,然后在合适位置插入即可。插入操作的实现过程可以阅读下一部分的代码。
若遍历到最后元素都没有插入,则将h设置为head最后一个元素的next,返回head。
(3)
void display(Task *head, int m);//输出第m项任务的信息,或者输出OK
从头遍历,遇到空值直接输出ok并退出。
遍历结束,未退出,直接输出该节点信息即可。
(4)
Task* study(Task*head, int mi, int ma, int load);
//mi是本次学习过程中,所学习的各个单项任务的最短持续时间约束,ma是单项任务的最长持续时间约束, load是本次学习过程的最长持续时间
首先检查列表是否为空。
然后检查头节点是否符合,若符合,则删除头节点。注意:删除头节点可以直接通过将头节点指针设置为当前头节点下一个元素实现。此后应该重新检查新的头节点,直到头节点没有下一个元素,即下一元素为空。
再次检查是否只剩余一个元素。
最后通过前后指针,每次检查后指针的元素,这样能够更容易实现断开重连的操作。直到后指针没有下一元素。
代码
Task *getBatch(int m) {
int k;
cin >> k;
Task *hh = new Task;
hh->next = NULL;
hh->key = k;
cin >> hh->ID >> hh->load;
Task *f = hh;
for (int i = 1; i < m; i++) {
Task *temp = new Task;
temp->key = k;
temp->next = NULL;
cin >> temp->ID >> temp->load;
f->next = temp;
f = f->next;
}
return hh;
}; //接收一批任务,该批任务共有m项任务
Task *addBatch(Task *head, Task *h) {
if (h == NULL)
return head;
if (head == NULL)
return h;
if (h->key < head->key) {
Task *f = h;
while (f->next != NULL) {
f = f->next;
}
f->next = head;
return h;
}
Task *temp = head;
while (temp->next != NULL) {
if (temp->next->key > h->key) {
Task *f = h;
while (f->next != NULL) {
f = f->next;
}
f->next = temp->next;
temp->next = h;
return head;
}
temp = temp->next;
}
temp->next = h;
return head;
};//将h指向的小链表插入head指向的大链表,并返回新的大链表首指针。
void display(Task *head, int m) {
if (head == NULL) {
cout << "OK" << endl;
return;
}
Task *temp = head;
for (int i = 1; i < m; i++) {
if (temp->next == NULL) {
cout << "OK" << endl;
return;
}
temp = temp->next;
}
cout << temp->ID << " " << temp->key << " " << temp->load << endl;
return;
};//输出第m项任务的信息,或者输出OK
Task *study(Task *head, int mi, int ma, int load) {
if (head == NULL)
return NULL;
Task *temp = head;
while (temp->next != NULL) { // 检查头节点
if (temp->load >= mi && temp->load <= ma && load >= temp->load) {
load -= temp->load;
head = head->next;
temp = head;
} else {
break;
}
}
if (temp->next == NULL) {
if (temp->load >= mi && temp->load <= ma && load >= temp->load) {
return NULL;
} else {
return head;
}
}
Task *f = head, *b = head->next;
while (b != NULL) {
if (b->load >= mi && b->load <= ma && load >= b->load) {
load -= b->load;
f->next = b->next;
b = b->next;
} else {
f = f->next;
b = b->next;
}
}
return head;
};
//mi是本次学习过程中,所学习的各个单项任务的最短持续时间约束,ma是单项任务的最长持续时间约束, load是本次学习过程的最长持续时间