[PAT解题报告] Course List for Student
题目本身不难,给定一些课程,还有哪些学生选了这个课,最后查询某些学生上了哪些课。
本来就是一个图的邻接表输出的问题,但是PAT在这个问题上卡时间和卡内存非常紧——所以用了一些诡异的技巧。
(1) 学生id是3个大写字母加一个数字。所以我们可以把这4个字符作一个26进制数。
(2)
2500门课程,每个课程id在1-2500以内,所以我可以用12bit(0-4095)表示这个课程。把(1)的数左移动12位,低12位表示这门课程。
(3) 于是((1) << 12) | (2) 这个整数表示一条边,也就是(1)这个学生选了(2)这门课程。
(4) 把(3)里面这些整数从小到大排序,可以知道id相同的学生在一起了,同一个学生不同的课程id由小到大排好顺序了。
(5) 查询一个学生的时候,先在(4)的数组里二分出第一个该id学生出现的位置——也就是查询 >= (id <<
12)的第一个位置,如果找到了x发现,发现x >> 12 == id,说明确实又这个学生的信息。
(6)
如果能找到这样的id,顺着这个id找下去就可以。但是为了提前知道个数,我们可以再二分一次,找到这个id最后一次出现的位置,然后顺着输出就可以了。
总之,这个题就是排序加二分的题目——把一个很简单的问题弄得复杂了。
代码:
#include <cstdio> #include <cstring> #include <map> #include <string> #include <algorithm> #include <vector> #include <cctype> using namespace std; const int w[] = {1, 26, 26 * 26, 26 * 26 * 26}; int all[500002]; int total; char s[10]; inline int make(char *s) { return (s[0] - 'A') * w[3] + (s[1] - 'A') * w[2] + (s[2] - 'A') * w[1] + (s[3] - '0'); } int find1(int x) { // first >= x int left = 0, right = total - 1, r = -1; while (left <= right) { int mid = (left + right) >> 1; if (all[mid] >= x) { r = mid; right = mid - 1; } else { left = mid + 1; } } return r; } int find2(int left,int x) { //last <= x int right = total - 1; ++left; while (left <= right) { int mid = (left + right) >> 1; if (all[mid] <= x) { left = mid + 1; } else { right = mid - 1; } } return left - 1; } int main() { int n,m; scanf("%d%d",&n,&m); for (int i = 0;i < m;++i) { int x,y; scanf("%d%d",&x,&y); for (;y;--y) { scanf("%s",s); all[total++] = (make(s) << 12) | x; } } sort(all, all + total); for (;n;--n) { scanf("%s",s); printf("%s",s); int x = make(s), y = find1(x << 12); if ((y < 0) || ((all[y] >> 12) > x)) { puts(" 0"); } else { x = find2(y, (x << 12) | m); printf(" %d",x - y + 1); for (;y <= x; ++y) { printf(" %d",all[y] & 4095); } puts(""); } } }
原题链接: http://www.patest.cn/contests/pat-a-practise/1039