这几天在看刘汝佳老师的算法竞赛入门经典这本书,前两章讲的主要是有关c语言的输入输出语句,条件语句,以及循环语句,这些都是课本有的,没什么难度。从第三章开始,尽管有关知识点都是学过的,但是一些题目所要运用的解题方法和思路非常简洁明了,也很全面,即使已经学了c++一学期了,有些题目对我来说是有挑战性的,相对于标准答案,我的方法显得有点冗余笨重。以下对一些有意思的题目(按照标准答案)做一下总结。
在这里插入代码片在n×n方阵里填入1,2,…,n×n,要求填成蛇形。例如,n=4时方阵为:
这个矩阵从右上角的1开始之后往下递增,到边界时往左,然后往右,往左,往下,往左….直到填满这个矩阵。这其实是一种循环的步骤,循环体里面有四个操作,即向下,向左,向上,向右。每当碰到边界就执行下一步,直到所有的数都填满了。如何来表示这个边界呢?
我们可以用一个常量二维0数组来初始化矩阵,当执行操作的时候往矩阵赋值,已经赋值了的就表明这个地方走过了,还没赋值的矩阵依旧是0,判断是否到达边界的方法就是,下一步遇到的矩阵元素的值是否改变了,如果改变了,则进行转向,依次循环,到最后所有的值都赋值完毕即可。下面是有关代码。
#include
#include
#define maxn 20
int a[maxn][maxn];
int main()
{
int n, x, y, tot = 0;
scanf("%d", &n);
memset(a, 0, sizeof(a)); //定义一个零数组
tot = a[x=0][y=n-1] = 1; //矩阵左上角为起点
while(tot < n*n)
{
while(x+1=0 && !a[x][y-1]) a[x][--y] = ++tot; //向左递增,直到边界
while(x-1>=0 && !a[x-1][y]) a[--x][y] = ++tot; //向上递增,直到边界
while(y+1
上面循环语句的特点是:先判断下一步是否能走,再执行。在很多情况下,最好是在做一件事之前检查是不是可以做,而不要做完再后 悔。因为“悔棋”往往比较麻烦。
所谓 回文串,就是反转以后和原串相同,如abba和madam。所有镜像串,就是左右镜像之后和原 串相同,如2S和3AIAE。下表一一列出键盘上的字母和数字对应的镜像字符。
题目:输入的每行包含一个字符串(保证只有上述字符。不含空白字符),判断它是否为回文 串和镜像串(共4种组合)。每组数据之后输出一个空行。
样例输入:
NOTAPALINDROME
ISAPALINILAPASI
2A3MEAS
ATOYOTA
样例输出:
NOTAPALINDROME – is not a palindrome.
ISAPALINILAPASI – is a regular palindrome.
2A3MEAS – is a mirrored string.
ATOYOTA – is a mirrored palindrome.
分析:本题判断一个串是否为回文串难度不大,只要将串逆序与原来串对比即可。但判断是否为竞赛串可就麻烦了,如果还是用逆序比的话,因为各个字符对应的镜像字符各不一致,所以就要分很多情况,如果一一列举的话,十分麻烦。其实换个思维想,可以利用字符与镜像字符对应的顺序建立数组,按照字符的相对顺序从数组调出对应的镜像字符进行逆序对比则可快速解决问题。代码如下:
#include
#include
#include
const char* rev = "A 3 HIL JM O 2TUVWXY51SE Z 8 ";//按照图片中字符顺序将镜像字符装进一个指针变量(相当于数组)
const char* msg[] = {"not a palindrome", "a regular palindrome", "a mirrored string", "a mirrored palindrome"};
char r(char ch) { //定义一个函数,返回ch字符的镜像字符
if(isalpha(ch)) return rev[ch - 'A'];//判断字符是否为字母,ch - 'A'表示该字母镜像字符的位置
return rev[ch - '0' + 25]; //ch - '0' + 25表示该数字镜像字符的位置
}
int main() {
char s[30];
while(scanf("%s", s) == 1) { //输入字符串
int len = strlen(s);
int p = 1, m = 1;
for(int i = 0; i < (len+1)/2; i++) {
if(s[i] != s[len-1-i]) p = 0; //不是回文串
if(r(s[i]) != s[len-1-i]) m = 0; //不是镜像串
}
printf("%s -- is %s.\n\n", s, msg[m*2+p]);
}
return 0;
}
如果x加上x的各个数字之和得到y,就说x是y的生成元。给出n(1≤n≤100000),求最小 生成元。无解输出0。例如,n=216,121,2005时的解分别为198,0,1979。
这道题大家很容易把关注点放在找生成元上面,就容易用循环判断的方法,判断从1到N是否存在n的生成元,如果这样的话,代码执行效率不高。不妨从找最小生成元为x的原数n入手,用一个数组在位置为n的元素中存储m,(n代表原数,m为 其最小生成元)之后再将其取出即可,代码如下
#include
#include
#define maxn 100005
int ans[maxn];
int main() {
int T, n;
memset(ans, 0, sizeof(ans));
for(int m = 1; m < maxn; m++) { //枚举所有的m 0) { y += x % 10; x /= 10; } //求出原数
if(ans[y] == 0 || m < ans[y]) ans[y] = m; //数组编号为原数,他的值为其最小生成元
}
scanf("%d", &T);
while(T- ) {
scanf("%d", &n); 从从 printf("%d\n", ans[n]); //输入原数n,返回他的最小生成元
}
return 0;
把手放在键盘上时,稍不注意就会往右错一 位。这样,输入Q会变成输入W,输入J会变成输 入K等。输入一个错位后敲出的字符串(所有字母均 大写),输出打字员本来想打出的句子。输入保 证合法,即一定是错位之后的字符串。例如输入中不会出现大写字母A。
样例输入:
O S, GOMR YPFSU/
样例输出:
I AM FINE TODAY.
此题用常量数组可大大简化代码,简单且方便操作,代码如下
#include
char s[] = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
int main() {
int i, c;
while((c = getchar()) != EOF) {
for (i=1; s[i] && s[i]!=c; i++); //找错位之后的字符在常量表中的位置
if (s[i]) putchar(s[i-1]); //如果找到,则输出它的前一个字符
else putchar(c);
}
return 0;
}
上面四道例题都有一个特点,都是运用数组巧妙解决了看似复杂的问题,一般情况下大多数人都只会用数组存贮多个元素以便输出这些元素。但其实数组也可以通过元素之间的关系巧妙地将两个相关元联系起来,即可以通过数组的次序关系找出对应的相关元,因此在以后遇到问题过程中,如果在特定的情况下,一些元素和另一些元素特定的联系且数量很多时可以利用常量数组解决,这将大大简化问题的复杂度。