PAT甲级题分类汇编——计算
本文为PAT甲级分类汇编系列文章。
计算类,指以数学运算为主或为背景的题。
题号 | 标题 | 分数 | 大意 |
1058 | A+B in Hogwarts | 20 | 特殊进制加法 |
1059 | Prime Factors | 25 | 分解素因数 |
1060 | Are They Equal | 25 | 一定精度下两数是否相等 |
1065 | A+B and C (64bit) | 20 | 大数加法与比较 |
1069 | The Black Hole of Numbers | 20 | 黑洞数 |
1073 | Scientific Notation | 20 | 科学计数法还原 |
1081 | Rational Sum | 20 | 有理数加法 |
1088 | Rational Arithmetic | 20 | 有理数运算 |
1096 | Consecutive Factors | 20 | 最长连续因数 |
1100 | Mars Numbers | 20 | 进制转换 |
这类题,普遍分数低,大多是20分。20分题放到乙级也没啥问题,比如1069、1081等。然而,即使是20分题,也可能要卡很久。
题目要求输出一个数的素因数分解。这么简单的题就不多讲了吧,直接上代码:
1 #include <iostream> 2 #include <map> 3 #include <cmath> 4 5 bool isprime(int n) 6 { 7 int end = std::sqrt(n) + 1; 8 for (int i = 2; i != end; ++i) 9 if (n % i == 0) 10 return false; 11 return true; 12 } 13 14 int main(int argc, char const *argv[]) 15 { 16 long n; 17 std::cin >> n; 18 std::cout << n << '='; 19 if (n == 1) 20 { 21 std::cout << '1'; 22 return 0; 23 } 24 std::map<int, int> factors; 25 int prime = 2; 26 while (n > 1) 27 { 28 if (isprime(prime) && n % prime == 0) 29 { 30 ++factors[prime]; 31 n /= prime; 32 } 33 else 34 ++prime; 35 } 36 int count = 0; 37 for (const auto& pair : factors) 38 { 39 if (count++) 40 std::cout << '*'; 41 std::cout << pair.first; 42 if (pair.second > 1) 43 std::cout << '^' << pair.second; 44 } 45 return 0; 46 }
其实用 std::vector 也是可以的,但我懒。
需要注意的是 n==1 的检查,不过这个很容易发现。
要求判断两个64位整数相加是否大于另一个数。注意两个64位整数相加以后如果还保存在64位整数中是可能溢出的,并且test case里面一定会有这种情况。
所以要用更大的整数来做计算。然而C++标准库中并没有提供128位整数。这时,伟大的g++出现了,它提供了 __int128_t :
1 #include <iostream> 2 3 int main(int argc, char const *argv[]) 4 { 5 int t; 6 std::cin >> t; 7 std::cout << std::boolalpha; 8 9 for (int i = 1; i <= t; ++i) 10 { 11 __int128_t a, b, c; 12 int64_t ta, tb, tc; 13 std::cin >> ta >> tb >> tc; 14 a = ta; b = tb; c = tc; 15 std::cout << "Case #" << i << ": " << (a + b > c) << std::endl; 16 } 17 18 return 0; 19 }
于是这道题就被秒了。
C++标准库对整数类型的支持不太好,大整数的类只能自己写。至于是以二进制存储还是以十进制存储,那要看应用场景了。
在一个case上卡了好久,最后发现题目里有一句要用 long int ,WCNM。
难度没什么的,主要是对分子和分母有0或1的特殊情况的处理。
优雅的我写了一份很优雅的实现,又是template又是运算符重载的:
1 #include <iostream> 2 #include <exception> 3 4 template <typename T> 5 class Rational 6 { 7 public: 8 Rational() = default; 9 Rational(T _num, T _den) 10 : numerator(_num), denominator(_den) 11 { 12 reduce(); 13 } 14 Rational(const Rational&) = default; 15 Rational& operator=(const Rational&) = default; 16 17 Rational operator+(const Rational& _rhs) const noexcept 18 { 19 return Rational(numerator * _rhs.denominator + _rhs.numerator * denominator, 20 denominator * _rhs.denominator); 21 } 22 Rational operator-(const Rational& _rhs) const noexcept 23 { 24 return Rational(numerator * _rhs.denominator - _rhs.numerator * denominator, 25 denominator * _rhs.denominator); 26 } 27 Rational operator*(const Rational& _rhs) const noexcept 28 { 29 return Rational(numerator * _rhs.numerator, 30 denominator * _rhs.denominator); 31 } 32 Rational operator/(const Rational& _rhs) const 33 { 34 if (_rhs.numerator == 0) 35 throw std::exception(); 36 return Rational(numerator * _rhs.denominator, 37 denominator * _rhs.numerator); 38 } 39 40 template <typename U> 41 friend std::istream& operator>>(std::istream&, Rational<U>&); 42 template <typename U> 43 friend std::ostream& operator<<(std::ostream&, const Rational<U>&); 44 private: 45 static T gcd(T _lhs, T _rhs) 46 { 47 if (_lhs < 0) 48 _lhs = -_lhs; 49 if (_lhs < _rhs) 50 return gcd(_rhs, _lhs); 51 if (_rhs == 0) 52 return _lhs ? _lhs : 1; 53 return gcd(_rhs, _lhs % _rhs); 54 } 55 56 void reduce() 57 { 58 if (denominator < 0) 59 { 60 numerator = -numerator; 61 denominator = -denominator; 62 } 63 auto factor = gcd(numerator, denominator); 64 numerator /= factor; 65 denominator /= factor; 66 } 67 68 T numerator; 69 T denominator; 70 }; 71 72 template <typename T> 73 std::istream& operator>>(std::istream& _is, Rational<T>& _r) 74 { 75 T n, d; 76 std::cin >> n; 77 std::cin.get(); 78 std::cin >> d; 79 _r = Rational<T>(n, d); 80 return _is; 81 } 82 83 template <typename T> 84 std::ostream& operator<<(std::ostream& _os, const Rational<T>& _r) 85 { 86 auto r = _r; 87 bool neg = false; 88 if (r.numerator < 0) 89 { 90 neg = true; 91 r.numerator = -r.numerator; 92 _os << "(-"; 93 } 94 if (r.denominator == 1) 95 _os << r.numerator; 96 else 97 { 98 if (r.numerator >= r.denominator) 99 { 100 _os << (r.numerator / r.denominator) << ' '; 101 r.numerator %= r.denominator; 102 } 103 _os << r.numerator << '/' << r.denominator; 104 } 105 if (neg) 106 _os << ')'; 107 return _os; 108 } 109 110 int main(int argc, char const *argv[]) 111 { 112 Rational<long long> lhs, rhs; 113 std::cin >> lhs >> rhs; 114 std::cout << lhs << " + " << rhs << " = " << (lhs + rhs) << std::endl; 115 std::cout << lhs << " - " << rhs << " = " << (lhs - rhs) << std::endl; 116 std::cout << lhs << " * " << rhs << " = " << (lhs * rhs) << std::endl; 117 std::cout << lhs << " / " << rhs << " = "; 118 try 119 { 120 std::cout << (lhs / rhs) << std::endl; 121 } 122 catch(const std::exception& e) 123 { 124 std::cout << "Inf" << std::endl; 125 } 126 return 0; 127 }
优雅个屁,考试要来不及的。
顺便讲讲写自定义类(特指有成员方法的类而非简单的结构体)和模板的技巧。
构造函数最好给一个默认的,Rule of Three/Five规定的方法最好显式 =default 写出来。
参数尽量用引用,看情况要不要加 const ,大多都是要加的。相应地,成员方法能用 const 修饰的一定要加上去。
对于模板类的友元函数,模板类中还要再套模板,而且要用不一样的类型名称。
以上规则如果不遵守,很可能会编译错误,而且错误信息看起来比较累。
要求输出给定整数的的分解中可以出现的最长连续因数。乍一看有一点难,但稍微想一想就会发现,这串因数不会太长,因为阶乘是很大的。所以可以一个一个试这串因数中的最小数,连续很多个因数只有没几种情况需要试,到连续两个的情况,也是O(sqrt(n))时间复杂度能搞定的。至于单个因数则需要单独讨论一下,因为试到根号n就可以了,而如果不停止的话会超时。
1 #include <iostream> 2 #include <utility> 3 #include <cmath> 4 5 int main(int argc, char const *argv[]) 6 { 7 int n; 8 std::cin >> n; 9 10 try 11 { 12 for (int count = 11; count > 1; --count) 13 { 14 for (int min = 2; ; ++min) 15 { 16 long product = 1; 17 for (int factor = min; factor != min + count; ++factor) 18 product *= factor; 19 if (product > n) 20 break; 21 if (n % product == 0) 22 throw std::pair<int, int>(count, min); 23 } 24 } 25 int end = std::sqrt(n) + 1; 26 for (int factor = 2; factor != end; ++factor) 27 if (n % factor == 0) 28 throw std::pair<int, int>(1, factor); 29 throw std::pair<int, int>(1, n); 30 } 31 catch(const std::pair<int, int>& pair) 32 { 33 std::cout << pair.first << std::endl; 34 for (int i = pair.second; i != pair.second + pair.first - 1; ++i) 35 std::cout << i << '*'; 36 std::cout << pair.second + pair.first - 1 << std::endl; 37 } 38 39 return 0; 40 }
长度的初始值是11,是因为2*3*...*13是大于2^31的,而2*3*...*12是小于2^31的,因此长度不会超过11。
总之,计算类题目没有难度,但是要避坑。坑依然是边界条件,包括最小的数(0、1、2等)与很大的数(int放不下的)。
posted on 2019-08-13 12:15 Jerry_SJTU 阅读(527) 评论(0) 编辑 收藏 举报