第05章_基础题目选解

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









 

你可能感兴趣的:(ACM,算法竞赛入门经典)