本文为PAT甲级分类汇编系列文章。
排序题,就是以排序算法为主的题。纯排序,用 std::sort 就能解决的那种,20分都算不上,只能放在乙级,甲级的排序题要么是排序的规则复杂,要么是排完序还要做点什么的。
在1051至1100中有6道:
题号 | 标题 | 分数 | 大意 | 时间 |
1055 | The World's Richest | 25 | 限定范围排序结果 | 500ms |
1056 | Mice and Rice | 25 | 分组排序 | 200ms |
1062 | Talent and Virtue | 25 | 一定规则的排序 | 400ms |
1075 | PAT Judge | 25 | 复杂排序 | 200ms |
1080 | Graduate Admission | 30 | 志愿与录取 | 250ms |
1083 | List Grades | 25 | 限定范围排序结果 | 400ms |
选了1056、1075和1080 3道做,其他不做是因为觉得太水了。
1056:
题目要求模拟晋级赛,每组选手中的最高分进入下一组,同一轮中淘汰的名次相同。
边界情况是只剩一个人,这时比赛就结束了,是循环的结束条件,所以也不算边界的坑了。
主循环中用到两个 std::vector<int> 对象,分别作为当前一轮的选手与晋级的选手,在循环的最后一个赋值一个清空。非常巧的是(也可能是必然),输入数据中的顺序刚好可以表示当前一轮。
很简单的题,一遍就AC了。
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 5 struct Programmer 6 { 7 int index; 8 int mice; 9 int score; 10 int rank; 11 }; 12 13 14 int main(int argc, char const *argv[]) 15 { 16 int total, per; 17 std::cin >> total >> per; 18 std::vector<Programmer> prog(total); 19 for (int i = 0; i != total; ++i) 20 prog[i].index = i, std::cin >> prog[i].mice; 21 std::vector<int> current(total); 22 for (int i = 0; i != total; ++i) 23 std::cin >> current[i]; 24 25 std::vector<int> next; 26 while (1) 27 { 28 auto iter = current.begin(); 29 int index; 30 while (iter != current.end()) 31 { 32 int max = -1; 33 for (int i = 0; i != per && iter != current.end(); ++i, ++iter) 34 if (prog[*iter].mice > max) 35 { 36 index = *iter; 37 max = prog[*iter].mice; 38 } 39 ++prog[index].score; 40 next.push_back(index); 41 } 42 if (next.size() == 1) 43 break; 44 current = next; 45 next.clear(); 46 } 47 48 std::sort(prog.begin(), prog.end(), [](const Programmer& lhs, const Programmer& rhs) { 49 return lhs.score > rhs.score; 50 }); 51 int count = 2; 52 prog.front().rank = 1; 53 for (auto iter = prog.begin() + 1; iter != prog.end(); ++iter, ++count) 54 if (iter->score == (iter - 1)->score) 55 iter->rank = (iter - 1)->rank; 56 else 57 iter->rank = count; 58 std::sort(prog.begin(), prog.end(), [](const Programmer& lhs, const Programmer& rhs) { 59 return lhs.index < rhs.index; 60 }); 61 auto end = prog.end() - 1; 62 for (auto iter = prog.begin(); iter != end; ++iter) 63 std::cout << iter->rank << ' '; 64 std::cout << end->rank << std::endl; 65 66 return 0; 67 }
1075:
这道题要求模拟PAT评分系统,统计一系列提交,最后按照用户来输出。麻烦的是没有提交、编译错误、满分等情况,之前看到这道题的时候就觉得太烦跳了。
要排好序,主要要搞清楚题目里的这些概念:总分、满分题数、有效用户,还有提交与输出分数的关系。
一个个来讲吧。总分就是所有分数加起来,这很简单。然而,由于-1表示编译错误的存在,累加不能直接相加,要判断是否大于0(大于等于也一样)。同时还需要一种表示没有提交过的方法,我就用-2了。
满分题数,就是把一个人的各题分数一个个和满分比较,所有相等的数量。这是排序中的第二关键字。
有效用户,就是有过编译通过的提交的用户。就算提交完是0分,也算有效用户,这个点坑到了。
提交与分数,坑在如果提交编译错误,这道题是算0分而不是算没提交,这个也坑到了。
区区一道25分题就放那么多坑,我下午放学开始写,晚上熄灯后才写完(虽然没有一直在写,至少加起来也一个多小时了,但主要是不AC我难受啊),姥姥你心不痛吗?
1 #include <iostream> 2 #include <iomanip> 3 #include <vector> 4 #include <algorithm> 5 6 int num_problem; 7 std::vector<int> problem_full; 8 9 class User 10 { 11 public: 12 User(int _id) 13 : id_(_id), problems(num_problem, -2) 14 { 15 ; 16 } 17 void submission(int _pro, int _score) 18 { 19 if (_score > problems[_pro]) 20 problems[_pro] = _score; 21 } 22 bool operator<(const User& _user) const 23 { 24 calculate(); 25 _user.calculate(); 26 if (score_ > _user.score_) 27 return true; 28 if (score_ < _user.score_) 29 return false; 30 if (perfect_ > _user.perfect_) 31 return true; 32 if (perfect_ < _user.perfect_) 33 return false; 34 return id_ < _user.id_; 35 } 36 bool valid() const 37 { 38 calculate(); 39 return valid_; 40 } 41 void rank(int _rank) 42 { 43 rank_ = _rank; 44 } 45 int rank() const 46 { 47 return rank_; 48 } 49 int score() const 50 { 51 calculate(); 52 return score_; 53 } 54 friend std::ostream& operator<<(std::ostream& _os, const User& _user); 55 private: 56 int id_; 57 std::vector<int> problems; 58 mutable bool calculated_; 59 mutable int score_; 60 mutable int perfect_; 61 mutable bool valid_; 62 int rank_; 63 void calculate() const 64 { 65 if (!calculated_) 66 { 67 calculated_ = true; 68 for (int i = 0; i != problems.size(); ++i) 69 if (problems[i] >= 0) 70 { 71 score_ += problems[i]; 72 if (problems[i] == problem_full[i]) 73 ++perfect_; 74 valid_ = true; 75 } 76 } 77 } 78 }; 79 80 std::ostream& operator<<(std::ostream& _os, const User& _user) 81 { 82 std::cout << std::setfill('0'); 83 _os << _user.rank_ << ' '; 84 _os << std::setw(5) << _user.id_ << ' '; 85 _os << _user.score_; 86 for (int s : _user.problems) 87 { 88 std::cout << ' '; 89 if (s >= 0) 90 std::cout << s; 91 else if (s == -1) 92 std::cout << '0'; 93 else 94 std::cout << '-'; 95 } 96 return _os; 97 } 98 99 int main(int argc, char const *argv[]) 100 { 101 int num_user; 102 int num_submission; 103 std::cin >> num_user >> num_problem >> num_submission; 104 105 std::vector<User> users; 106 users.reserve(num_user); 107 for (int i = 0; i != num_user; ++i) 108 { 109 users.emplace_back(i + 1); 110 } 111 problem_full.reserve(num_problem); 112 for (int i = 0; i != num_problem; ++i) 113 { 114 int t; 115 std::cin >> t; 116 problem_full.push_back(t); 117 } 118 for (int i = 0; i != num_submission; ++i) 119 { 120 int user, pro, score; 121 std::cin >> user >> pro >> score; 122 --user; 123 --pro; 124 users[user].submission(pro, score); 125 } 126 std::sort(users.begin(), users.end()); 127 int count = 1; 128 users.front().rank(count++); 129 for (auto iter = users.begin() + 1; iter != users.end(); ++iter, ++count) 130 { 131 if (iter->score() == (iter - 1)->score()) 132 iter->rank((iter - 1)->rank()); 133 else 134 iter->rank(count); 135 } 136 for (const auto& u : users) 137 if (u.valid()) 138 std::cout << u << std::endl; 139 else 140 break; 141 142 return 0; 143 }
为了优雅,我把代码写得很OO(其实是object-based)。虽然也没有人会去复用它。
还有一点,测试数据里的case 4很诡异,我提交了3次,时间分别是191、110、194ms。更关键的是这道题限制就200ms,我要是再写烂一点不就超时了吗?还一会超时一会不超时的,搞不懂。
1080:
这道题要求模拟志愿录取,核心算法在于分配而不是排序,之前读题的时候没注意,给分到这里来了。
输入、排序、输出都很简单,难在分配,即所谓录取过程。要是没有并列的都要录取这条规则,对排序完的学生遍历一遍就可以了,但它偏要有这条,不过谁让它是30分题呢。讲真,我感觉这道30分比上一道25分简单,一遍AC。
我解决并列问题的方法是这样的:一对iterator,分别指向并列一段的起始和尾后,然后将学校名额“锁住”,保持它是否有空余名额的状态,这时往里塞。就算超名额也不管,是题目要求的。“锁住”以后也不用“解锁”,下次“锁住”的时候会更新状态(也许应该换个名字,毕竟lock以后不unlock怪怪的)。
1 #include <iostream> 2 #include <vector> 3 #include <algorithm> 4 #include <functional> 5 #include <utility> 6 using std::rel_ops::operator>; 7 8 struct School 9 { 10 int quota; 11 std::vector<int> admitted; 12 bool free() 13 { 14 return free_; 15 } 16 void lock() 17 { 18 free_ = admitted.size() < quota; 19 } 20 private: 21 bool free_; 22 }; 23 24 struct Student 25 { 26 int id; 27 int grade_e; 28 int grade_i; 29 std::vector<int> choices; 30 int rank; 31 bool operator<(const Student& _rhs) const 32 { 33 if (grade_e + grade_i < _rhs.grade_e + _rhs.grade_i) 34 return true; 35 if (grade_e + grade_i > _rhs.grade_e + _rhs.grade_i) 36 return false; 37 return grade_e < _rhs.grade_e; 38 } 39 bool operator==(const Student& _rhs) const 40 { 41 return grade_e == _rhs.grade_e && grade_i == _rhs.grade_i; 42 } 43 }; 44 45 int main() 46 { 47 int num_applicant, num_school, num_choice; 48 std::cin >> num_applicant >> num_school >> num_choice; 49 std::vector<School> schools(num_school); 50 std::vector<Student> students(num_applicant); 51 for (auto& s : schools) 52 std::cin >> s.quota; 53 for (int i = 0; i != num_applicant; ++i) 54 { 55 auto& s = students[i]; 56 s.id = i; 57 std::cin >> s.grade_e >> s.grade_i; 58 s.choices.resize(num_choice); 59 for (auto& i : s.choices) 60 std::cin >> i; 61 } 62 std::sort(students.begin(), students.end(), std::greater<Student>()); 63 for (auto iter = students.begin() + 1; iter != students.end(); ++iter) 64 if (*iter == *(iter - 1)) 65 iter->rank = (iter - 1)->rank; 66 else 67 iter->rank = (iter - 1)->rank + 1; 68 auto end = students.begin(); 69 while (end != students.end()) 70 { 71 auto iter = end; 72 while (end != students.end() && *end == *iter) 73 ++end; 74 for (auto& s : schools) 75 s.lock(); 76 for (; iter != end; ++iter) 77 { 78 for (const auto& s : iter->choices) 79 if (schools[s].free()) 80 { 81 schools[s].admitted.push_back(iter->id); 82 break; 83 } 84 } 85 } 86 for (auto& s : schools) 87 { 88 std::sort(s.admitted.begin(), s.admitted.end()); 89 if (!s.admitted.empty()) 90 { 91 auto end = s.admitted.end() - 1; 92 for (auto iter = s.admitted.begin(); iter != end; ++iter) 93 std::cout << *iter << ' '; 94 std::cout << *end; 95 } 96 std::cout << std::endl; 97 } 98 }
自己用 operator< 来实现 operator> 太烦了,我选择 std::rel_ops 。
还有一个星期就要考了,我10篇才写了3篇,可以不用睡觉了。