本文爲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道題,決定作1054、1071、1082和1095一共4道。spa
放在線性分類下,就要用線性方法作。用 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吧,我沒試過。
字符串的線性處理、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 }
這道題放在線性類,是由於配對和模擬的算法都是線性的(實際上是由於我看到它是模擬類就把它分給線性類了)。
總之,線性類題目難度不高,但坑很多,不只有各類邊界數據,還有卡時間常數的。