第05章_基础题目选解
Example_050101_WERTYU_字符替换.cpp
Example_050103_周期串.cpp
Example_050201_小学生算术_进位次数判断.cpp
Example_050202_阶乘的精确值.cpp
Example_050301_6174问题_排序.cpp
Example_050302_字母重排.cpp
Example_050401_Cantor的数表.cpp
Example_050402_因子和阶乘.cpp
这一章节及接下来的章节,都只列出例题的题目,至于练习,会在另一个新的分类[ACM]分类中更新,因为那属于OnlineJudge的题目的,方便到时候翻阅,待大部份章节学习完后,会弄一个总目录出来。
Example_050101_WERTYU_字符替换.cpp
// Example_050101_WERTYU_字符替换.cpp /** * 题目名称:WERYU * 题目描述: * 把手放在键盘上时,稍不注意就会往后错一位。这样的话,Q会变成W,J会变成K等。 * 输入一个错位后敲出的字符串,输出打字员本来想打出的句子。 * 样例输入:O S, GOMR YPFSU/ * 样例输出:I AM FINE TODAY. **/ // 我们可以使用常量数组,这样就不用一个个switch进行判断。 #include <cstdio> using namespace std; char * s = "'1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./"; int main() { int i, c; while((c = getchar()) != EOF){ for(i = 1; s[i] && s[i] != c; ++i); // 找到该字符的位置为止,s[i]作为判断可找到字符标志,防溢出 if(s[i]){ // 当它不是结束符 putchar(s[i-1]); }else{ // 否则,输出原字符 putchar(c); } } return 0; }
Example_050103_周期串.cpp
// Example_050103_周期串.cpp /** * 题目名称: 周期串 * 题目描述: * 如果一个字符串可以由某个长度为k的字符串重复多次得到,我们说该串以k为周期。 * 例如,abcabcabcabc以3为周期(注意,它也以6和12为周期)。输入一个长度不超过80的串, * 输出它的最小周期。 * 样例输入: HoHoHo * 样例输出: 2 **/ /** * 题目分析思路: * 1. 划分周期 2. 剩余字符串与第一周期的字符串对比 * 对于这种找字符串周期的题目,可以先尝试设置一个循环,然后从递增的变量中划分周期, * 如果能成功划分的话,就可以下一步了,先进行划分这步,可以减少判断的次数,缩少比较范围。 * 接下来第二步,以刚刚划分的周期为周期,尝试将周期内的每一个字符与剩下的周期对比, * 如假设周期为3,原字符串为abcaccabc,则它会对比1-a和4-a, 2-b和5-c,当发现不同时,证明此周期非所求周期, * 当对比至最后一字符也没有重复的周期时,说明这个字符串的周期即为整个字符串的长度。 **/ #include <cstdio> #include <string> #include <cstring> using namespace std; int main() { char word[100]; scanf("%s", word); int len = strlen(word); for(int i = 1; i <= len; ++i){ if (len % i == 0){ // 划分周期 int ok = 1; for(int j = i; j < len; ++j){ // 各周期间字符比较 if(word[j] != word[j % i]){ ok = 0; break; } } if (ok){ printf("%d\n", i); break; } } } return 0; }
Example_050201_小学生算术_进位次数判断.cpp
// Example_050201_小学生算术_进位次数判断.cpp /** * 题目名称: 小学生算术 * 题目描述: * 计算两个不超过9个数字的整数在相加时需要多少次进位。编制程序可连续处理多组数据,直到读到两个0. * 输出它的最小周期。 * 样例输入: 123 456 * 555 555 * 123 594 * 样例输出: 0 * 3 * 1 **/ /** * 题目分析思路: * 1. 先知道程序的输入和输出数值范围 * 输入时为9位整数,已知int型可保存2.4*pow(10, 10),因此,直接使用int计算也可以保证不溢出。 * 2. 分析加法运算规律及思路:这里,可以总结为,低位运算,进位累计,数据左移,再运算累计,直至左移到最高位。 * 回顾最基本的加法方法:如: 5678+1234 * 5678 * +1234 * ----- * 6912 * 在该式子中,“低位运算”指:8+4 = 12, “进行累计”指:这里,8与4相加进位了,则+1, * “数据左移”指:每进行完一个位数的相加,则将两个加数一起左移一个数位继续相加, * 即,此时变为567+123+c(进位)(加法的进位只可能是0或1) * “再运算累计”,此时7+3+1为11,运算结果还是有进位,+1,继续这样的循环,直至将9个位数还有第十位进位都加完为止。 **/ #include <cstdio> using namespace std; int main() { int a, b; while(2 == scanf("%d%d", &a, &b){ // 返回成功赋值的变量的数值 if(!a && !b) { return 0; } // 当其值都为零时,则退出程序。 int c = 0; int ans = 0; for(int i = 9; i >= 0; --i){ // 开始对每位进行加法运算,加数的位数最多为9位, // 取十次循环 i >= 0 是因为加上可能会进位的那一位数 c = (a % 10 + b % 10 + c) > 9 ? 1 :0; // 取a与b的最低位进行加法进算,数值超过9则可进位 ans += c; // 进位结果累计至ans中 a /= 10; b /= 10; // 将两位加数左移一位 } printf("%d\n", ans); } return 0; }
Example_050202_阶乘的精确值.cpp
// Example_050202_阶乘的精确值.cpp /** * 题目名称: 阶乘的精确值 * 题目描述: 输入不超过1000的正整数n,输出n! = 1 * 2 * 3 * 4 * ... * n 的精确结果。 * 样例输入: 30 * 样例输出: 265252859812191058636308480000000 **/ /** * 题目分析: * 1. 从条件确定数值范围: * 这里的输入的最大正整数为n <= 1000,需知道的是1000的阶乘约等于4*pow(10, 2567)。 * 若是用数组中的一个元素保存数值的一位数,则可用3000个元素的数组f保存。 * 2. 知道数学乘法计算规则分析程序中可以用到的算法: * 上一题中提到,加法可以逐位数相加,乘法的原理也差不多:假如 1234(n[j]) * 56(i) * 56 // i作为正在阶乘过程的某一个数 * * 1234 // 这里的j代表每一位数 * ------ // 运用数学的乘法规则 * 224 // 先用第j = 0位数4,去乘以56,得到224,这时,可以把4作为这次乘法运算的j的位数结果 * 168 // 这时,可以用上面的224(进位)的结果,先左移一位,得到22,然后,3*56得168,再将168与22相加 * // 得到的尾数即为j = 1的数值结果,如此类推 * 112 * 56 * ------- * 69104 // 所以,就这样,得到全部位数的结果了。循环每次阶乘中的数i,不断更新这数,就可以得到最终结果了。 **/ #include <cstdio> #include <string> using namespace std; const int maxn = 3000; int f[maxn]; int main() { int i, j, n; scanf("%d", &n); memset(f, 0, sizeof(f)); // 将数组内全部元素值设0 f[0] = 1; // 先将个位数设置为1,因为乘法时避免将值归零 for(i = 2; i <= n; ++i){ // 乘以i int c = 0; // 进位标记及进位数值记录 for(j = 0; j < maxn; ++j){ // 从最低位开始操作,将这个阶乘的i,乘 int s = f[j] * i + c; // 保存乘法的结果到s中,s = j位数*i阶乘到的数值+进位数值c f[j] = s % 10; // 将乘得的结果取模,得最低位放到f[j]指定位数中(f[j]存一位的数值) c = s / 10; // 将剩余的进位数保留,至同一阶乘数i中的对下一位位数j进行计算。 } } for( j = maxn - 1; j >= 0; --j){ // 忽略前导零,即如:00123,这些零不要。 if (f[j]){ break; } } for( i = j; i >= 0; --i){ // 遍历输出数组 printf("%d", f[i]); } printf("\n"); return 0; }
Example_050301_6174问题_排序.cpp
// Example_050301_6174问题_排序.cpp /** * 题目名称: 6174问题 * 题目描述: * 假设你有一个各位数字互不相同的四位数,把所有的数字从小到大排序后得到a, * 从大到小排序后得到b,然后用a-b替换原来这个数,并且继续操作。例如:从1234出发, * 依次得到4321-1234=3087、8730-0378=8352、8532-2358=6174。有趣的是,7641-1467=6174,回到它自己。 * 输入一个n位数,输出操作序列,直到出现循环即新得到的数曾经得到过)。输入保证在循环之前 * 最多只会产生1000个整数。 * 样例输入:1234 * 样例输出:1234 -> 3087 -> 8352 -> 6174 -> 6174 **/ /** * 题目分析: * 直接根据题目意思,将算法一步步写下即可。 * 其中需要注意的是, * 第一,sprintf(s, "%d", x);将整型数据转化为字符型。 * 第二,这里使用的冒泡法思路是先将一位标志位定住,再遍历数组,然后找出最值放入标志位, * 移动标志位,进入下一轮比较。 * 第三,将字符串反转,除了将整个字符串复制一次倒向输出外,还可以将字符串“对折”这样的思路。 **/ #include <cstdio> #include <string> using namespace std; int get_next(int x) { int a, b, n; char s[10]; // 转化为字符串 sprintf(s, "%d", x); n = strlen(s); // 冒泡排序 for(int i = 0; i < n; ++i){ for(int j = n+1; j < n; ++j){ if (s[i] > s[j]){ char t = s[i]; s[i] = s[j]; s[j] = t; } } } sscanf(s, "%d", &b); // 字符串反转 for(int i = 0; i < n/2; ++i){ // 这种反转方法相当于沿中线对折 char t = s[i]; s[i] = s[n - 1 - i]; s[n - 1 - i] = t; } sscanf(s, "%d", &a); return a - b; } int num[2000]; int main() { scanf("%d", &num[0]); printf("%d", num[0]); int Count = 1; for(;;){ // 生成并输出下一个数 num[Count] = get_next(num[Count - 1]); printf(" -> %d", num[Count]); // 在数组num中寻找新生成的数 int found = 0; for ( int i = 0; i < Count; ++i){ if (num[i] == num[Count]){ found = 1; break; } } if (found){ break; // 如果找到相同的数值,则退出循环; } ++Count; } printf("\n"); system("pause"); return 0; }
Example_050302_字母重排.cpp
// Example_050302_字母重排.cpp /** * 题目名称: 字母重排 * 题目描述: * 输入一个字典(用******结尾),然后再输入若干单词。每输入一个单词w, 你都需要在字典中 * 找出所有可以用w的字母重排后得到的单词,并按照字典序从小到大的顺序在一行中输出(如果 * 不存在,输出:( )。输入单词之间用空格或空行隔开,且所有输入单词不超过6个小写字母组成。 * 样例输入: * trap given score refund only trap work earn course pepper part * ****** * resco nfudre aptr sett oresuc * 样例输出: * score * refund * part trap trap * :( * course **/ /** * 题目分析: * 较容易想到的方法: * 1) 每读入一个单词,就和字典中的所有单词比较,看看是否可以通过重排得到。 * 2) 把可以重排得到的单词放在一个数组中。 * 3) 把这个数组排序后输出。 * 显然,这种方法,可能会消耗大量的时间去每个单词做比较。 * 我们可以在读入时就把每个单词按照字母排好序,就不必每次对比一个单词就重排一次了。 **/ #include <cstdio> #include <cstdlib> #include <string> #include <cstring> using namespace std; int n; char word[2000][10]; // 用于保存用户输入的字典数组 char sorted[2000][10]; // 用于对这个字典数组中的每个元素排好序后的数组。 // 字符比较函数 int cmp_char(const void* _a, const void* _b) { char* a = (char*)_a; char* b = (char*)_b; return *a - *b; } // 字符串比较函数 int cmp_string(const void* _a, const void* _b) { char* a = (char*)_a; char* b = (char*)_b; return strcmp(a, b); } // 开始main函数前,先了解一个在stdlib库中的函数qsort(...) // #include <stdlib.h> // void qsort( void *buf, size_t num, size_t size, int (*compare)(const void *, const void *) ); // 功能: 对buf 指向的数据(包含num 项,每项的大小为size)进行快速排序。 // 如果函数compare 的第一个参数小于第二个参数,返回负值;如果等于返回零值;如果大于返回正值。 // 函数对buf 指向的数据按升序排序。 int main() { n = 0; for(;;) { scanf("%s", word[n]); if(word[n][0] == '*') break; // 遇到结束标志则终止循环 n++; } qsort(word, n, sizeof(word[0]), cmp_string); // 给所有单词排序 for(int i = 0; i < n; i++) { // copy数组并给每个单词排序 strcpy(sorted[i], word[i]); qsort(sorted[i], strlen(sorted[i]), sizeof(char), cmp_char); } char s[10]; // 第三排,逐个单词读取 while(scanf("%s", s) == 1) { // 持续读取到文件结尾 qsort(s, strlen(s), sizeof(char), cmp_char); // 给输入的单词排序 int found = 0; for(int i = 0; i < n; i++) if(strcmp(sorted[i], s) == 0) { found = 1; printf("%s ", word[i]); // 输出原始单词,而不是排序后的 } if(!found) printf(":("); printf("\n"); } return 0; }
Example_050401_Cantor的数表.cpp
// Example_050401_Cantor的数表.cpp /** * 题目名称: Cantor的数表 * 题目描述: * 如下列数,第一项是1/1, 第二项是1/2,第三项是2/1, 第四项是3/1,第五项是2/2, ...输入n,输出第n项。 * 1/1 1/2 1/3 1/4 1/5 * 2/1 2/2 2/3 2/4 * 3/1 3/2 3/3 * 4/1 4/2 * 5/1 * 样例输入: * 3 * 14 * 7 * 12345 * 样例输出: * 2/1 * 2/4 * 1/4 * 59/99 **/ /** * 题目分析: * 1) 找出数表分布规律:我们可以将这数表顺时针旋转45度来看: * 1/1 * 1/2 2/1 * 3/1 2/2 1/3 * 1/4 2/3 3/2 4/1 * 5/1 4/2 3/3 2/4 1/5 * 从上往下看,数出每一行的元素个数,则可以发现: * 行-元素个数, 1-1, 2-2, 3-3, i-i, 由等差数列,s(n) = (a1 + an) * an / 2 * s(k) = 1 + 2 + 3 + ... + k = (1+k)*k/2 * * 2) 确定第n项位置的方法: * 为了确定n在哪条斜线上,我们需要找到一个最小正整数k,使得n<=s(k), * 那么n就是第k条斜线上的倒数第s(k)-n+1个元素(最后一个元素是倒数第1个元素,而不是倒数第0个元素)。 * 在看第i个元素是什么元素时,还要注意一个规律是,当其为奇数行时,分子降序,分母升序,而偶数行则相反。 * 即:奇数行:i/(k+1-i), 偶数行: (k+1-i)/i * 或者,在程序中,除了可以用if-else的除2取模的方法判断外, * 还可以直接使用幂函数:pow((i/(k+1-i)), pow(-1, (i+1))); **/ #include <cstdio> #include <cmath> using namespace std; int main() { int n; while(scanf("%d", &n) == 1){ int k = 1; int s = 0; for(;;){ s += k; if(s >= n){ // 所求项是第k条对角线的倒数第s-n+1个元素 if(0 != k % 2){ // 奇数行 printf("%d/%d\n", s-n+1, k-s+n); }else{ printf("%d/%d\n", k-s+n, s-n+1); } break; } ++k; } } return 0; }
Example_050402_因子和阶乘.cpp
// Example_050402_因子和阶乘.cpp /** * 题目名称: 因子和阶乘 * 题目描述: * 输入正整数n(2<=n<=10),把阶乘n! = 1*2*3*...*n分解成素因子相乘的形式从小到大输出各个 * 素数(2, 3, 5, ...)的指数。例如825 = 3*5^2*11应表示成(0, 1, 2, 0, 1), 表示分别有0、1、2、0、1个 * 2、3、5、7、11.你的程序应忽略比最大素因子更大的素数(否则末尾会有无穷多个0)。 * 样例输入: * 5 * 53 * 样例输出: * 5! = 3 1 1 * 53! = 49 23 12 8 4 4 3 2 2 1 1 1 1 1 1 1 **/ /** * 题目分析: * 1. 构建素数表 * 2. 再申请与素数表一样大小的数组,用于存放素数表中元素对应的素数次方数。 * 3. 在阶乘过程中,使用阶乘的元素去除以能整除的素数,逐次记录,直至将所有的阶乘元素都除完为止。 **/ #include <cstdio> #include <string> #include <cstring> using namespace std; // 素数判定。注意,n不能太大,容易溢出,不过现数值在题目限定范围内不会出问题 int is_prime(int n) { for (int i = 2; i * i <= n; ++i){ if (n % i == 0){ return 0; } } return 1; } // 素数表 int prime[100]; int Count; int main() { // n和各个素数的指数 int n; int p[100]; // 构造素数表 for ( int i = 2; i <= 100; ++i){ if ( is_prime(i)){ prime[Count++] = i; } } while(1 == scanf("%d", &n)){ printf("%d! =", n); memset(p, 0, sizeof(p)); // 在创数组时 int p[100] = {0};也能初始化数组值全为0 int maxp = 0; for(int i = 1; i <= n; ++i){ int m = i; // m为阶乘过程中的元素,此处申请变量为下面的计算作准备 for(int j = 0; j < Count; ++j){ // 反复除以prime[j],并累加p[j] while(m % prime[j] == 0){ m /= prime[j]; ++p[j]; if(j > maxp){ maxp = j; // 更新最大素因子的下标 } } } } for(int i = 0; i <= maxp; ++i){ printf(" %d", p[i]); } printf("\n"); } return 0; }