PAT甲级题分类汇编——线性

本文为PAT甲级分类汇编系列文章。html

 

线性类,指线性时间复杂度能够完成的题。在1051到1100中,有7道:ios

题号 标题 分数 大意 时间
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分,但也不难。git

这种题通常一看就会作(最大子列和除外),难度通常在细节处理(全部PAT题都是)和时间常数上。算法

关于细节处理,分数低的题,原本就简单,作的时候容易想起一些细节问题,在第一次提交以前就处理好了。性能

关于时间限制,其余题时间限制通常都是400ms,但线性类时间更严格的比较多,这时候就要当心常数了。优化

 

仔细阅读了一下7道题,决定作1054107110821095一共4道。spa

 

1054:指针

放在线性分类下,就要用线性方法作。用 std::map 作起来一看就很简单,为了挑战本身,换一种方法。code

大体思路是每一个维度(颜色)创建一个散列表,表中存放指向下一个维度的散列表的指针,最后一个维度存放数据。htm

代码以下:

 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吧,我没试过。

 

1071:

字符串的线性处理、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必定大于这个长度,所以这道题是线性的。

 

1082:

呵,这道题就是在考数学。但我没有把它放到数学那一类中,由于这是小学数学。

一开始以为很难,但只要把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 }

这是我第一次用拼音来命名变量。我也不想这样,但谁让这道题中文背景这么明显呢?

 

1095:

这道题要求模拟车辆进出校园的过程,这类模拟一个过程的题目,我称之为模拟类题。

模拟类题的一个通用方法就是以时间为变量循环,可是这道题写着写着就用上另外一种方法了,就是以对象为变量循环,这里的对象就是车辆进出记录。

题目逻辑比较复杂,大体能够分为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 }

这道题放在线性类,是由于配对和模拟的算法都是线性的(实际上是由于我看到它是模拟类就把它分给线性类了)。

 

总之,线性类题目难度不高,但坑很多,不只有各类边界数据,还有卡时间常数的。

相关文章
相关标签/搜索