Euler枚举
1.E22:Name score 在文本文件names.txt 中包含了五千多个名字,
首先将它们按照字母序排列,然后计算出每个名字的字母价值,乘以它在按字母顺序排列后的位置,就算出了这个名字的得分。
例如,按照字母序排列后,位于第938位的名字是COLIN,它的字母价值是3 + 15 + 12 + 9 + 14 = 53
因此,COLIN这个名字的得分是 938 × 53 = 49714.
上述文本文件中,所有名字的得分之和是多少?
1.暴力枚举
将name.txt文件下载下来,进行初步处理%s/","/ /g
(将连接符替换为空格分隔)、%s/"/ /g
(处理开头结尾的单引号)
将名字输入保存到name[6005]数组中
首先使用sort算法对name数组进行排序
外循环遍历name数字中的每一个名字,内循环遍历name的每一个字母计算每个名字的价值value
输出最后累加的score分数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> #include <string> #include <algorithm> using namespace std;string name[6005 ]; int n;int main () { while (cin >> name[n]){ n++; } sort (name, name + n); long long score = 0 ; for (int i = 0 ; i < n; ++i){ int value = 0 ; for (int j = 0 ; j < name[i].size (); ++j){ value += name[i][j] - 'A' + 1 ; } score += value * (i + 1 ); } cout << score << endl; return 0 ; }
补充:txt.name文件重定向输入操作./a.out < input22
2.E32:Pandigital products 如果一个n位数包含了1至n的所有数字恰好一次,我们称它为全数字的;例如,五位数15234就是1至5全数字的。
7254是一个特殊的乘积,因为在等式39 × 186 = 7254中,被乘数、乘数和乘积恰好是1至9全数字的。
找出所有被乘数、乘数和乘积恰好是1至9全数字 的乘法等式,并求出这些等式中乘积的和 。
注意:有些乘积可能从多个乘法等式中得到,但在求和的时候只计算一次。(需要对乘积去重)
1.暴力枚举 两层for循环不断尝试乘数与被乘数(乘积确定),动态判定结果是否符合全数字要求、乘积是否重复出现,符合则计入ans
枚举的范围:
外层for循环范围,至少应从2开始(若为1乘数与成积重复必不为全数字),至多到98结束(3位数乘积将达到6位数必不符合)
内层for循环范围,应该从j=i+1
开始(乘数与被乘数重复),可暂时设置为死循环(在循环体中当i
、j
、i*j
位数和大于9结束)
对于全数字的判断
开辟一个mark[10]
标记数组,将被判断的数字拆分存入标记数组中,查看当遍历结束时标记数组中是否全为1(如果满足则+=ans
)
对于乘积相同的等式重复判断
开辟一个check[10005]
标记数组,如果乘积第1次出现则计入累加,否则乘积重复不记入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <cmath> using namespace std;int digit (int x) { return floor (log10 (x)) + 1 ; } int work (int *mark, int num) { while (num){ int temp = num % 10 ; if (mark[temp] == 1 ){ return 0 ; } mark[temp] = 1 ; num /= 10 ; } return 1 ; } int func (int x, int y, int z) { int mark[10 ] = {0 }; if (work (mark, x) && work (mark, y) && work (mark, z) && mark[0 ] == 0 ){ return 1 ; } return 0 ; } int main () { int ans = 0 , check[10005 ] = {0 }; for (int i = 2 ; i < 99 ; ++i){ for (int j = i + 1 ; 1 ; ++j){ int a = digit (i), b = digit (j), c = digit (i * j); if (a + b + c > 9 ){ break ; } if (a + b + c == 9 ){ if (func (i, j, i * j)){ cout << i << " " << j << " = " << i * j << endl; if (check[i * j] == 0 ){ check[i * j] = 1 ; ans += i * j; } } } } } cout << ans << endl; return 0 ; }
总结感悟:对于枚举法最重要的是确定枚举的范围,以及符合条件的结果判断
3.E33:Digit cancelling fractions 49/98是一个有趣的分数,缺乏经验的数学家可能在约简时错误地认为等式49/98 = 4/8
之所以成立是因为在分数线上下同时抹除了9的缘故。
我们也会想到,存在诸如30/50 = 3/5这样的平凡解。
这类有趣的分数恰好有四个非平凡的例子,它们的分数值小于1,且分子和分母都是两位数。
将这四个分数的乘积写成最简分数,求此时分母的值。
1.暴力枚举
枚举范围:外层for循环范围11~99,内层for循环范围i+1
-99
对于非凡解的判定:将约去同一数字后的分数与约去前的分数交叉相乘,若相等则为非凡解(对于平凡解的去除,任一数字不为0)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #include <iostream> using namespace std;int func (int x, int y) { int x1 = x / 10 , x2 = x % 10 , y1 = y / 10 , y2 = y % 10 ; if (!x1 || !x2 || !y1 || !y2){ return 0 ; } if (x1 == y1 && x * y2 == y * x2) return 1 ; if (x1 == y2 && x * y1 == y * x2) return 1 ; if (x2 == y1 && x * y2 == y * x1) return 1 ; if (x2 == y2 && x * y1 == y * x1) return 1 ; return 0 ; } int gcd (int a, int b) { if (b == 0 ){ return a; } return gcd (b, a % b); } int main () { int a = 1 , b = 1 ; for (int i = 11 ; i < 100 ; ++i){ for (int j = i + 1 ; j < 100 ; ++j){ if (func (i, j)){ cout << i << " / " << j << endl; a *= i; b *= j; } } } int temp = gcd (a, b); a /= temp; b /= temp; cout << a << " / " << b << endl; return 0 ; }
注意:熟练辗转相除法求最大公因数
4.E36:Double-base palindromes 十进制数585 = 10010010012 (二进制表示),因此它在这两种进制下都是回文数。
找出所有小于一百万 ,且在十进制和二进制下均为回文的数,并求它们的和。
(请注意无论在哪种进制下,回文数均不考虑前导零 。)
1.暴力枚举 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> using namespace std;int func (int num, int x) { int raw = num; int temp = 0 ; while (num){ temp = temp * x + num % x; num /= x; } return temp == raw; } int main () { int ans = 0 ; for (int i = 1 ; i < 1000000 ; ++i){ if (func (i, 10 ) && func (i, 2 )){ cout << i << endl; ans += i; } } cout << ans << endl; return 0 ; }
5.E30:Digit fifth powers 令人惊讶的是,只有三个数可以写成其各位数字的四次幂之和:
1634 = 1^4 + 6^4 + 3^4 + 4^4 8208 = 8^4 + 2^4 + 0^4 + 8^4 9474 = 9^4 + 4^4 + 7^4 + 4^4
由于1 = 1^4
并不是求和,所以这里不计入内。
上面这三个数的和是1634 + 8208 + 9474 = 19316
找出所有可以写成其各位数字的五次幂之和的数 ,并求这些数的和。
1.暴力枚举
枚举范围如何确定?
首先考虑时间效率优化,对于1-9
的五次幂可以先计算存储到一个数组中(避免每次重复计算,提升时间效率)
利用关系(该数字 = 其各位数字的五次幂之和)确定枚举范围,
假设该数字的位数为n ,则有该数字取值为[1-10n ),五次幂之和的取值范围为[n * 05 , n * 9n ]
故另10n = n * 9n 则可以解出该数字位数n的最大值,从而确定枚举范围(n的取值大致为5~6之间,==枚举范围取6==)
符合要求答案的条件:各位数字的五次幂之和等于该数字,则符合题目条件进行累加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> using namespace std;int penta[15 ];void init () { for (int i = 1 ; i < 10 ; ++i){ int temp = 1 ; for (int j = 0 ; j < 5 ; ++j){ temp *= i; } penta[i] = temp; cout << i << ":" << penta[i] << " " << endl; } } int func (int num) { int raw = num; int temp = 0 ; while (num){ temp += penta[num % 10 ]; num /= 10 ; } return temp == raw; } int main () { init (); int ans = 0 ; for (int i = 2 ; i < 1000000 ; ++i){ if (func (i)){ ans += i; cout << i << endl; } } cout << ans << endl; return 0 ; }
相似题:E34-Digit factorials
2.总结: 刷题总结:对于数字num进行==各位5次幂求和==的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int penta[15 ];void init () { for (int i = 1 ; i < 10 ; ++i){ int temp = 1 ; for (int j = 0 ; j < 5 ; ++j){ temp *= i; } penta[i] = temp; cout << i << ":" << penta[i] << " " << endl; } } int func (int num) { int raw = num; int temp = 0 ; while (num){ temp += penta[num % 10 ]; num /= 10 ; } return temp == raw; }