第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;
}