线性类,指线性时间复杂度可以完成的题。在1051到1100中,有7道:
| 题号 | 标题 | 分数 | 大意 | 时间 |
| 1054 | The Dominant Color | 20 | 寻找出现最多的数 | 200ms |
| 1061 | Dating | 20 | 寻找字符串中相同字符 | 200ms |
| 1071 | Speech Patterns | 25 | 寻找出现最多的单词 | 300ms |
| 1077 | Kuchiguse | 20 | 字符串共同后缀 | 150ms |
| 1082 | Read Number in Chinese | 25 | 中文读数 | 400ms |
| 1084 | Broken Keyboard | 20 | 比较两序列的差异 | 200ms |
| 1095 | Cars on Campus | 30 | 模拟车辆进出 | 300ms |
可以看到线性题一般分数不高,一般只有模拟事件的题会出30分,但也不难。
这种题一般一看就会做(最大子列和除外),难度一般在细节处理(所有PAT题都是)和时间常数上。
关于细节处理,分数低的题,本来就简单,做的时候容易想起一些细节问题,在第一次提交之前就处理好了。
关于时间限制,其他题时间限制一般都是400ms,但线性类时间更严格的比较多,这时候就要小心常数了。
仔细阅读了一下7道题,决定做1054、1071、1082和1095一共4道。
放在线性分类下,就要用线性方法做。用 std::map 做起来一看就很简单,为了挑战自己,换一种方法。
大致思路是每个维度(颜色)建立一个散列表,表中存放指向下一个维度的散列表的指针,最后一个维度存放数据。
代码如下:
1 #include <iostream>
2 #include <vector>
3 #include <memory>
4
5 template <typename T>
6 using Pointer_vector = std::shared_ptr<std::vector<T>>;
7
8 int main()
9 {
10 int m, n, total;
11 std::cin >> m >> n;
12 total = m * n;
13 using Blue = int;
14 using Green = Pointer_vector<Blue>;
15 using Red = Pointer_vector<Green>;
16 std::vector<Red> data(256);
17 for (int cnt = 0; cnt != total; ++cnt)
18 {
19 int color;
20 std::cin >> color;
21 int red = color >> 16;
22 int green = (color >> 8) % 256;
23 int blue = color % 256;
24 auto& red_data = data[red];
25 if (!red_data)
26 red_data = Red(new std::vector<Green>(256));
27 auto& green_data = (*red_data)[green];
28 if (!green_data)
29 green_data = Green(new std::vector<Blue>(256));
30 auto& blue_data = (*green_data)[blue];
31 ++blue_data;
32 }
33 try
34 {
35 for (int r = 0; r != 256; ++r)
36 if (data[r])
37 for (int g = 0; g != 256; ++g)
38 if ((*data[r])[g])
39 for (int b = 0; b != 256; ++b)
40 if ((*(*data[r])[g])[b] > total / 2)
41 throw (r << 16) + (g << 8) + b;
42 }
43 catch (int res)
44 {
45 std::cout << res;
46 }
47 }
对于多重循环要 break 的算法我一般用异常来做流控。这是一种备受争议的做法,因此我又想着用输出和 return 替换掉原来的 throw 语句,没想到竟然超时了!加了优化也没用。
不是说 try block会导致性能下降的吗?为什么加了异常处理以后性能反而提高?
想不通。我又用 std::map 实现了一个算法:
1 #include <iostream>
2 #include <map>
3
4 #pragma GCC optimize(O3)
5
6 int main()
7 {
8 int m, n, total;
9 std::cin >> m >> n;
10 total = m * n;
11 std::map<int, int> map;
12 for (int cnt = 0; cnt != total; ++cnt)
13 {
14 int color;
15 std::cin >> color;
16 ++map[color];
17 }
18 for (const auto& pair : map)
19 if (pair.second > total / 2)
20 {
21 std::cout << pair.first;
22 return 0;
23 }
24 return 0;
25 }
一开始当然是没有 #pragma 那一行的,但是超时了,后来加上了才AC。
这道题告诉我们,线性题对时间要求真的很高。或许用 scanf 代替 std::cin 能让原本超时的AC吧,我没试过。
字符串的线性处理、std::map 的使用,这道题难度就这么多了。代码如下:
1 #include <iostream>
2 #include <string>
3 #include <map>
4 #include <cctype>
5
6 int main()
7 {
8 std::map<std::string, int> words;
9 std::string string;
10 while (1)
11 {
12 char c = std::cin.get();
13 if (std::isalnum(c))
14 string.push_back(std::tolower(c));
15 else
16 if (!string.empty())
17 {
18 ++words[string];
19 string.clear();
20 }
21 if (c == '\n')
22 break;
23 }
24 auto max = words.cbegin();
25 for (auto iter = words.cbegin(); iter != words.cend(); ++iter)
26 if (iter->second > max->second)
27 max = iter;
28 std::cout << max->first << ' ' << max->second << std::endl;
29 }
值得杠一杠的是这道题是不是线性。
首先,输入是O(n),输出是O(1),都在线性范围内。
假设一共有a种单词,对 std::map 的操作是O(a·loga);a种单词连起来的最小长度是O(a·loga),n一定大于这个长度,因此这道题是线性的。
呵,这道题就是在考数学。但我没有把它放到数学那一类中,因为这是小学数学。
一开始觉得很难,但只要把10000以内的数字怎么读搞清楚了,这道题就差不多了。代码如下:
1 #include <iostream>
2 #include <string>
3 #include <vector>
4
5 std::vector<std::string> data;
6
7 const char pinyin[10][5] =
8 {
9 "ling",
10 "yi",
11 "er",
12 "san",
13 "si",
14 "wu",
15 "liu",
16 "qi",
17 "ba",
18 "jiu"
19 };
20
21 void chinese(int num)
22 {
23 int qian = num / 1000;
24 num %= 1000;
25 int bai = num / 100;
26 num %= 100;
27 int shi = num / 10;
28 int ge = num % 10;
29 int digit = 0;
30 if (qian)
31 {
32 digit = 3;
33 data.push_back(pinyin[qian]);
34 data.push_back("Qian");
35 }
36 if (bai)
37 {
38 digit = 2;
39 data.push_back(pinyin[bai]);
40 data.push_back("Bai");
41 }
42 if (shi)
43 {
44 if (digit > 2)
45 data.push_back(pinyin[0]);
46 digit = 1;
47 data.push_back(pinyin[shi]);
48 data.push_back("Shi");
49 }
50 if (ge)
51 {
52 if (digit > 1)
53 data.push_back(pinyin[0]);
54 data.push_back(pinyin[ge]);
55 }
56 }
57
58 int main()
59 {
60 int num;
61 std::cin >> num;
62 if (num == 0)
63 {
64 std::cout << pinyin[0];
65 return 0;
66 }
67 if (num < 0)
68 {
69 num = -num;
70 data.push_back("Fu");
71 }
72 int yi = num / 100000000;
73 int wan = num % 100000000 / 10000;
74 int ge = num % 10000;
75 int seg = 0;
76 if (yi)
77 {
78 seg = 2;
79 data.push_back(pinyin[yi]);
80 data.push_back("Yi");
81 }
82 if (wan)
83 {
84 if (wan < 1000 && seg > 1)
85 data.push_back(pinyin[0]);
86 seg = 1;
87 chinese(wan);
88 data.push_back("Wan");
89 }
90 if (ge)
91 {
92 if (ge < 1000 && seg > 0)
93 data.push_back(pinyin[0]);
94 chinese(ge);
95 }
96 int end = data.size() - 1;
97 for (int i = 0; i != end; ++i)
98 std::cout << data[i] << ' ';
99 std::cout << data[end];
100 }
这是我第一次用拼音来命名变量。我也不想这样,但谁让这道题中文背景这么明显呢?
这道题要求模拟车辆进出校园的过程,这类模拟一个过程的题目,我称之为模拟类题。
模拟类题的一个通用方法就是以时间为变量循环,但是这道题写着写着就用上另一种方法了,就是以对象为变量循环,这里的对象就是车辆进出记录。
题目逻辑比较复杂,大致可以分为3个步骤:
第一步,读取所有记录,并按时间顺序排序,然后按记录类型配对;
第二步,读取查询的时间,并模拟车辆进出的过程,这是模拟类题的核心(但在本题中占的比例不大);
第三步,找出最长的停车时间,并输出对应的车牌号(老司机开车!)。
梳理完这堆逻辑以后就直接上代码吧:
1 #include <iostream>
2 #include <iomanip>
3 #include <string>
4 #include <vector>
5 #include <map>
6 #include <algorithm>
7
8 int read_time()
9 {
10 int res, temp;
11 std::cin >> temp;
12 res = temp * 3600;
13 std::cin.get();
14 std::cin >> temp;
15 res += temp * 60;
16 std::cin.get();
17 std::cin >> temp;
18 res += temp;
19 return res;
20 }
21
22 void print_time(int time)
23 {
24 std::cout << std::setfill('0');
25 std::cout << std::setw(2) << time / 3600 << ':';
26 time %= 3600;
27 std::cout << std::setw(2) << time / 60 << ':';
28 std::cout << std::setw(2) << time % 60;
29 }
30
31 enum class Status
32 {
33 in, out
34 };
35
36 struct Record
37 {
38 std::string plate;
39 int time;
40 Status status;
41 bool paired = false;
42 int parking;
43 };
44
45 int main()
46 {
47 int n, k;
48 std::cin >> n >> k;
49 std::vector<Record> records(n);
50 for (auto& r : records)
51 {
52 std::cin >> r.plate;
53 r.time = read_time();
54 std::string str;
55 std::cin >> str;
56 r.status = str == "in" ? Status::in : Status::out;
57 }
58 std::sort(records.begin(), records.end(), [](const Record& _lhs, const Record& _rhs) {
59 return _lhs.time < _rhs.time;
60 });
61 for (auto rec = records.begin(); rec != records.end(); ++rec)
62 if (rec->status == Status::in)
63 for (auto iter = rec + 1; iter != records.end(); ++iter)
64 if (iter->plate == rec->plate && iter->status == Status::in)
65 break;
66 else if (iter->plate == rec->plate && iter->status == Status::out)
67 {
68 rec->paired = iter->paired = true;
69 rec->parking = iter->time - rec->time;
70 break;
71 }
72
73 auto iter = records.begin();
74 int count = 0;
75 for (int cnt = 0; cnt != k; ++cnt)
76 {
77 int time = read_time();
78 for (; iter != records.end() && iter->time <= time; ++iter)
79 if (iter->paired && iter->status == Status::in)
80 ++count;
81 else if (iter->paired && iter->status == Status::out)
82 --count;
83 std::cout << count << std::endl;
84 }
85
86 std::map<std::string, int> parking;
87 for (const auto& rec : records)
88 if (rec.paired && rec.status == Status::in)
89 parking[rec.plate] += rec.parking;
90 int longest = 0;
91 for (const auto& car : parking)
92 if (car.second > longest)
93 longest = car.second;
94 for (const auto& car : parking)
95 if (car.second == longest)
96 std::cout << car.first << ' ';
97 print_time(longest);
98 }
这道题放在线性类,是因为配对和模拟的算法都是线性的(其实是因为我看到它是模拟类就把它分给线性类了)。
总之,线性类题目难度不高,但坑不少,不仅有各种边界数据,还有卡时间常数的。