c语言函数的深入学习(递归调用、控制运行时间)

A - Specialized Four-Digit Numbers

• 试题来源:ACM Pacific Northwest 2004
• 在线测试:POJ 2196,ZOJ 2405,UVA 3199

问题重述:

• 找到并列出所有具有这样特性的十进制的4位数字:其4位数字的和等于这个数字以16进制表示时的4位数字的和,也等于这个数字以12进制表示时的4位数字的和。
• 例如,整数2991的(十进制)4位数字之和是 2+9+9+1 = 21,因为 2991 = 11728 + 8144 + 9*12 + 3,所以其12进制表示为189312,4位数字之和也是21。但是2991的十六进制表示为BAF16,并且11+10+15 = 36,因此2991被程序排除了。
• 下一个数是2992,3种表示的各位数字之和都是22 (包括BB016),因此2992要被列在输出中。(本题不考虑少于4位数字的十进制数----排除了前导零----因此2992是第一个正确答案。)

输入

• 本题没有输入。

输出

• 输出为2992和所有比2922大的满足需求的4位数字(以严格的递增序列),每个数字一行,数字前后不加空格,以行结束符结束。输出没有空行。输出的前几行如下所示。

答案

#include<stdio.h>
int sumful(int num,int digit);
int main() {
    
    
	int num;
	int i;
	for(i=2991; i<10000; i++) {
    
    
		if(sumful(i,10)==sumful(i,12)&&sumful(i,10)==sumful(i,16))
			printf("%d\n",i);
	}
}
int sumful(int num,int digit) {
    
    
	int i=0,sum=0;
	while (num>0) {
    
    
		sum+=num%digit;
		num=num/digit;
	}
	return sum;
}

B - Pig-Latin

• 试题来源:University of Notre Dame Local Contest 1995
• 在线测试:UVA 492

问题重述

您意识到PGP加密系统还不足够保护您的电子邮件,所以,你决定在使用PGP加密系统之前,先把您的明文字母转换成Pig Latin(一种隐语),以完善加密。
请您编写一个程序,输入任意数量行的文本,并以Pig Latin输出。
每行文本将包含一个或多个单词。一个“单词”被定义为一个连续的字母序列(大写字母和/或小写字母)。单词根据以下的规则转换为Pig Latin,非单词的字符在输出时则和输入中出现的完全一样:
[1] 以元音字母(a、e、i、o或u,以及这些字母的大写形式)开头的单词,要在其后面附加字符串“ay”(不包括双引号)。例如,“apple”变成“appleay”。
[2] 以辅音字母(不是A, a, E, e, I, i, O, o, U 或 u的任何字母)开头的单词,要去掉第一个辅音字母,并将之附加在单词的末尾,然后再在单词的末尾加上“ay”。例如,“hello”变成“ellohay”。
[3] 不要改变任何字母的大小写。

问题分析

在本题中,我原本的思路是设置多个函数执行以下作用:

  1. 判读单个字符的是否是字母,若不是就处理掉,若是,就调用函数2
  2. 判断是否为元音,是则调用函数3,否则调用函数4
  3. 处理元音字母,并重新调用函数1,继续向下处理
  4. 处理辅音字母,并重新调用函数1,继续向下处理

虽然可以靠着函数的互相嵌套调用执行题目,但是很不幸,在VJUDGE的编译器里超时了。

#include<stdio.h>
#include<string.h>
#include<cctype>
void ischar(char s[]);//判断是否字母并处理 
void isvowel(char s[]);//判断是否元音 
void vowel(char s[]);//元音字母处理方式 
void consonant(char s[]);//辅音字母处理方式 
char s[10000]= {
    
    '\0'};
int main() {
    
    
	gets(s);
	ischar(s);
}
void ischar(char s[]) {
    
    
	if(s[0]=='\0')
		return ;

	if (isalpha(s[0]))
		isvowel(s);
	else {
    
    
		printf("%c",s[0]);
		ischar(s+1);
	}
}
void isvowel(char s[]) {
    
    
	if( s[0]=='a'||s[0]=='e'||s[0]=='i'||s[0]=='o'||s[0]=='u'||
	        s[0]=='A'||s[0]=='E'||s[0]=='I'||s[0]=='O'||s[0]=='U')
		vowel(s);
	else consonant(s);
}
void vowel(char s[]) {
    
    
	int i=0;
	while(isalpha(s[i])) {
    
    
		printf("%c",s[i]);
		i++;
	}
	printf("ay");
	ischar(s+i);
}
void consonant(char s[]) {
    
    
	int i=1;
	while(isalpha(s[i])) {
    
    
		printf("%c",s[i]);
		i++;
	}
	printf("%cay",s[0]);
	ischar(s+i);
}

之后不得不放弃了这种互相调用的做法,参考了学长的思路,全部在主函数里面完成:

  1. 读入单个字符,判断是否为字母
  2. 如果是字母,则存入空的字符串s中, 并再次执行步骤1,直到读入的不是字母。
  3. 如果不是字母,则先判断s是否为空,若为空,则直接输出字符,若不为空,则先按要求输出此字符串,再输出字符。

源代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1e6 + 10;
char s[N], cnt;
int main() {
    
    
	char c;
	while(~scanf("%c", &c)) {
    
    
		if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
    
    
			s[cnt++] = c;
		} else {
    
    
			if(cnt == 0) {
    
    
				printf("%c", c);
				continue;
			}
			if(s[0] == 'a' || s[0] == 'e' || s[0] == 'i' || s[0] == 'o' || s[0]== 'u'
			        || s[0] == 'A' || s[0] == 'E' || s[0] == 'I' || s[0] == 'O' ||s[0] == 'U') {
    
    
				for(int i = 0; i < cnt; i++) {
    
    
					printf("%c", s[i]);
				}
				printf("ay%c", c);
			} else {
    
    
				for(int i = 1; i < cnt; i++) {
    
    
					printf("%c", s[i]);
				}
				printf("%cay%c", s[0], c);
			}
			cnt = 0;
		}
	}
	return 0;
}

C - Tic Tac Toe

• 在线测试:POJ 2361,ZOJ 1908,UVA 10363
• 试题来源:Waterloo local 2002.09.21
题目描述

输入

• 输入的第一行给出N,表示测试用例的数目。然后给出
4N-1行,说明N个用空行分隔的网格图。

输出

• 对于每个测试用例,在一行中输出"yes"或"no",表示该
网格图是否是有效的三连棋游戏的一个步骤。

试题解析

思路

原思路仍然是利用函数嵌套,又臭又长,还超时了,不解释了,上代码

#include<stdio.h>
void judge();
void iswin();
int winjudge();
void win(int n);

char chess[4][4];
int main() {
    
    
	int N;
	scanf("%d",&N);
	getchar();
	int i=0,j=0,k=0;
	for(i=0; i<N; i++) {
    
    
		for (j=0; j<3; j++) {
    
    
			for( k=0; k<3; k++) {
    
    
				chess[j][k]=getchar();
			}
			if(j!=2)
				getchar();
		}
		if(i!=N-1)
			getchar();
		judge();
	}
	return 0;
}
void judge() {
    
    
	int countX=0,countO=0;
	int i,j;
	for(i=0; i<3; i++) {
    
    
		for(j=0; j<3; j++) {
    
    
			if(chess[i][j]=='X')
				countX++;
			if(chess[i][j]=='O')
				countO++;
		}
	}
	if(countX-countO>1||countO-countX>0) {
    
    
		printf("no\n");
		return ;
	}
	iswin();
}
void iswin() {
    
    
	switch(winjudge()) {
    
    
		case 0:
		case 4:
			printf("yes\n");
			return;
		case 1:
			win(1) ;
			return ;
		case 2:
			win(2) ;
			return ;
		default:
			printf("no\n");
			return;
	}
}
int winjudge() {
    
    
	int count=0;
	int bI=0,bJ=0;
	int fI=0,fJ=0;
	for(fI=0,fJ=2; bI<3; bI++,fI++)
		if(chess[bI][bJ]==chess[fI][fJ]&&
		        chess[bI][bJ]==chess[(bI+fI)/2][(bJ+fJ)/2]) {
    
    
			if(chess[bI][bJ]=='X')
				count+=2;
			if(chess[bI][bJ]=='O')
				count+=1;
		}
	for(bI=0,bJ=0,fI=2,fJ=0; bJ<3; bJ++,fJ++)
		if(chess[bI][bJ]==chess[fI][fJ]&&
		        chess[bI][bJ]==chess[(bI+fI)/2][(bJ+fJ)/2]) {
    
    
			if(chess[bI][bJ]=='X')
				count+=2;
			if(chess[bI][bJ]=='O')
				count+=1;
		}
	if(chess[0][0]==chess[1][1]&&chess[0][0]==chess[2][2]) {
    
    
		if(chess[1][1]=='X')
			count+=2;
		if(chess[1][1]=='O')
			count+=1;
	}
	if(chess[0][2]==chess[1][1]&&chess[0][2]==chess[2][0]) {
    
    
		if(chess[1][1]=='X')
			count+=2;
		if(chess[1][1]=='O')
			count+=1;
	}
	return count;
}

void win(int n) {
    
    
	int countX=0,countO=0;
	int i,j;
	for(i=0; i<3; i++) {
    
    
		for(j=0; j<3; j++) {
    
    
			if(chess[i][j]=='X')
				countX++;
			if(chess[i][j]=='O')
				countO++;
		}
	}
	if(n==1)
		if (countX!=countO) {
    
    
			printf("no");
			return ;
		}

	if(n==1)
		if ((countX-countO)!=1) {
    
    
			printf("no");
			return;
		}
	printf("yes");
}

学长的思路是利用布尔变量去遍历数组是否已经获胜,很容易看得懂

#include <cstdio>
#include <iostream>
using namespace std;
char mp[3][3];
int x, o;
int flag;
bool cal(char tar) {
    
    
	for (int i = 0; i < 3; ++i) {
    
    
		if (mp[0][i] == tar) {
    
    
			if (mp[1][i]==tar && mp[2][i]==tar) {
    
    
				return true;
			}
		}
	}
	for (int i = 0; i < 3; ++i) {
    
    
		if (mp[i][0] == tar) {
    
    
			if (mp[i][1]==tar && mp[i][2]==tar) {
    
    
				return true;
			}
		}
	}
	if (mp[0][0]==tar && mp[1][1]==tar && mp[2][2]==tar) {
    
    
		return true;
	}
	if (mp[0][2]==tar && mp[1][1]==tar && mp[2][0]==tar) {
    
    
		return true;
	}
	return false;
}
int main() {
    
    
	int n;
	cin >> n;
	while (n--) {
    
    
		flag = x = o = 0;
		for (int i = 0; i < 3; ++i) {
    
    
			scanf("%s", mp[i]);
		}
		for (int i = 0; i < 3; ++i) {
    
    
			for (int j = 0; j < 3; ++j) {
    
    
				if (mp[i][j] == 'X') {
    
    
					x++;
				} else if (mp[i][j] == 'O') {
    
    
					o++;
				}
			}
		}
		if (x >= o) {
    
    
			if (x == o) {
    
    
				flag = cal('X') ? 0 : 1;
			} else if (x - o == 1) {
    
    
				flag = cal('O') ? 0 : 1;
			} else {
    
    
				flag = 0;
			}
		} else {
    
    
			flag = 0;
		}
		printf("%s\n", flag==1 ? "yes" : "no");
	}
	return 0;
}

E - Function Run Fun

题目重述:

我们都爱递归!不是吗?(可不敢)
请考虑一个三参数的递归函数w(a, b, c):
if a <=0 or b <= 0 or c <= 0, then w(a, b, c) 返回1;
if a > 20 or b > 20 or c > 20, then w(a, b, c) 返回w(20, 20, 20);
if a < b and b < c, then w(a, b, c) 返回w(a, b, c-1)+w(a, b-1, c-1)- w(a, b-1, c);
否则,返回w(a-1, b, c) + w(a-1, b-1, c) + w(a-1, b, c-1) - w(a-1, b-1, c-1)。
这是一个很容易实现的函数。但存在的问题是,如果直接实现,对于取中间值的a、b和c(例如,a=15,b=15,c=15),由于大量递归,程序运行非常耗时。

输入

• 程序的输入是一系列整数三元组,每行一个,一直到结束标志-1-1-1为止。请您高效地计算w(a, b, c)并输出结果。

输出

• 输出每个三元组w(a, b, c)的值。

解题思路

利用记忆化递归法,使用一个三维数组保存已经递归完毕的函数,减少程序耗时。
值得注意的是,不能刚进入函数就去判断是否在三维数组出现过,否则容易出现数组越界的情况。因此,要在第二的步骤完成之后(即a,b,c都小于20的情况下),再去判断是否在三维数组中出现。

源代码如下:
#include<iostream>
#include<cstring>
using namespace std;
int f[21][21][21];
int w(int a,int b,int c) {
    
    
	if(a<=0||b<=0||c<=0)
		return 1;
	if(a>20||b>20||c>30)
		return w(20,20,20);

	if(f[a][b][c])
		return f[a][b][c];

	if(a<b&&b<c)
		return f[a][b][c]=w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c);
	return f[a][b][c]=w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
}
int main() {
    
    

	memset(f, 0, sizeof(f));
	int a, b, c;
	while (cin >> a >> b >> c) {
    
    
		if (a == -1 && b == -1 && c == -1)break;
		cout <<"w("<<a<<", "<<b<<", "<<c<<") = "<< w(a, b, c) <<
		     endl;
	}
	return 0;
}

F - Simple Addition

问题重述:

输入

• 输入包含若干行。每行给出两个非负整数p和q(p≤q),这两个整数之间用一个空格隔开,p和q是32位有符号整数。输入由包含两个负整数的行结束。程序不用处理这一行。

输出

• 对于每行输入,输出一行,给出S(p, q)的值。

问题分析

根据递归函数F(n)的定义给出递归函数。因为p和q是32位有符号整数,S(p, q)的值可能会超出32位有符号整数,所以本题的变量类型定义为long long int,即64位有符号整数。

递归算法:

每一轮求出[p, q]区间内数字的个位数的和,并计入总和;再将[p, q]区间内个位数为0的数除以10,产生新区间,进入下一轮,再求新区间内数字的个位数的和,并计入总和,而个位数为0的数除以10,产生新区间;以此类推;直到[p, q]区间内的数只有个位数。
例如求S(2, 53),将范围划分为3个区间:[2, 9],[10, 50]和[51,53]。
对于第1个区间[2, 9],个位数之和2+3+4+……+9=44;对第2个区间[10, 50],个位数之和(1+2+……+9)4= 454 = 180;对于第3个区间[51, 53],个位数之和1+2+3 = 6。所以,第一轮,个位数的总和为44+180+6 = 230。
值得注意的是: 在[10, 50]中,10, 20, 30, 40和50的个位数是0,将这些数除以10后得1, 2, 3, 4, 5,产生新区间[1, 5];进入第二轮,区间[1, 5]中的数只有个位数,个位数之和1+2+3+4+5=15。(即此处需要递归)
最后,两轮结果相加,得S(2, 53)=230+15=245。
以下为自己的答案

源代码:

#include<stdio.h>
#include<iostream>
using namespace std;
int f(int x) {
    
    
	if(x%10>0)
		return x%10;
	if(x==0)
		return 0;
	return f(x/10);
}
long long int s(int p,int q) {
    
    
	int sum=0;
	for(; p<=q; p++) {
    
    
		sum+=f(p);
	}
	return sum;
}
long long f(int p,int q) {
    
    
	if(q-p<9)
		return s(p,q);
	int node1,node2;
	long long int sum=0;
	//头尾
	node1=(p/10+1)*10;
	node2=(q/10)*10;
	sum+=s(p,node1-1)+s(node2+1,q);
	//中间
	sum+=s(1,9)*(node2-node1)/10;
	return sum+f(node1/10,node2/10);
}
int main() {
    
    
	int p,q;
	while(scanf("%d%d",&p,&q)!=EOF) {
    
    
		cout << f(p,q) <<endl;
	}
	return 0;
}

G - A Contesting Decision

问题重述

• 对程序设计竞赛进行裁判是一项艰苦的工作,要面对要求严格的参赛选手,要作出乏味的决定,以及要进行着单调的工作。不过,这其中也可以有很多的乐趣。
• 对于程序设计竞赛的裁判来说,用软件使得评测过程自动化是一个很大的帮助,而一些比赛软件存在的不可靠也得使人们希望比赛软件能够更好、更可用。您是竞赛管理软件开发团队中的一员。
基于模块化设计原则,您所开发模块的功能是为参加程序设计竞赛的队伍计算分数并确定冠军。给出参赛队伍在比赛中的情况,确定比赛的冠军。
记分规则:
• 一支参赛队的记分由两个部分组成:第一部分是被解出的题数,第二部分是罚时,表示解题总的耗费时间和试题没有被解出前错误的提交所另加的罚时。对于每个被正确解出的试题,罚时等于该问题被解出的时间加上每次错误提交的20分钟罚时。在问题没有被解出前不加罚时。
• 因此,如果一支队伍在比赛20分钟的时候在第二次提交解出第1题,他们的罚时是40分种。如果他们提交第2题3次,但没有解决这个问题,则没有罚时。如果他们在120分钟提交第3题,并一次解出的话,该题的罚时是120分。这样,该队的成绩是罚时160分,解出了两道试题。
• 冠军队是解出最多试题的队伍。如果两队在解题数上打成平手,那么罚时少的队是冠军队。

输入

• 程序评判的程序设计竞赛有4题。本题设定,在计算罚时后,不会导致队与队之间不分胜负的情况。
• 第1行 为参赛队数n
• 第2 - n+1行为每个队的参赛情况。每行的格式
• < Name > < p1Sub > < p1Time > < p2Sub > < p2Time > … < p4Time >
• 第一个元素是不含空格的队名。后面是对于4道试题的解题情况(该队对这一试题的提交次数和正确解出该题的时间(都是整数))。如果 没有解出该题,则解题时间为0。如果一道试题被解出,提交次数至少是一次。

输出

• 输出一行。给出优胜队的队名,解出题目的数量,以及罚时。

问题分析

思路非常简单明确,但是提交的时候我崩溃了,c++的编译器为什么不支持struct类数组的初始化?!例如,输入struct stu stu[50]={0,0,0,0},这居然是不合法的。由此不得不手动给结构体数组的元素附上0。

自己的代码

#include<stdio.h>
#include<stdlib.h>
struct team_info {
    
    
	char name[20];
	int c1;
	int t1;
	int c2;
	int t2;
	int c3;
	int t3;
	int c4;
	int t4;
	int sumc;
	int sumt;
};

int main() {
    
    
	int n;
	scanf("%d",&n);
	struct team_info team[n];
	int i=0;
	for(i=0; i<n; i++)
		scanf("%s %d %d %d %d %d %d %d %d",
		team[i].name,
		      &team[i].c1,&team[i].t1,&team[i].c2,&team[i].t2,
		      &team[i].c3,&team[i].t3,&team[i].c4,&team[i].t4);
	for(i=0; i<n; i++) {
    
    
		if(team[i].t1) {
    
    
			team[i].t1+=(team[i].t1-1)*20; 
			team[i].sumt+=team[i].t1;
			team[i].sumc++;
		}
		if(team[i].t2) {
    
    
			team[i].t2+=(team[i].t2-1)*20;
			team[i].sumt+=team[i].t2;
			team[i].sumc++;
		}
		if(team[i].t3) {
    
    
			team[i].t3+=(team[i].t3-1)*20;
			team[i].sumt+=team[i].t3;
			team[i].sumc++;
		}
		if(team[i].t4) {
    
    
			team[i].t4+=(team[i].t4-1)*20;
			team[i].sumt+=team[i].t4;
			team[i].sumc++;
		}
	}
	int max=0;
	for(i=1; i<n; i++) {
    
    
		if(team[i].sumc>team[max].sumc)
			max=i;
	}
	int bin[n],count=0,flag=0;
	for(i=0; i<n; i++) {
    
    
		if(team[i].sumc==team[max].sumc) {
    
    
			flag=1;
			bin[count]=i;
			count++;
		}
	}
	int champion=max;
	if(flag==1) {
    
    
		for(i=0; i<count; i++) {
    
    
			if(team[bin[i]].sumt<team[champion].sumt) {
    
    
				champion=bin[i];
			}
		}
	}
	printf("%s %d %d",
	       team[champion].name,team[champion].sumc,team[champion].sumt);
}

不知道为什么提交的时候报错,很明显,自己的代码有很多要改进的地方,如用函数去减少代码长度,或者在for循环中直接完成对于分数总和的计算之类的。
请参考以下代码

正确答案代码:

#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 7;//为啥都喜欢申请这么大的地址,不会占内存吗 
const int inf = 0x3f3f3f3f;
struct node {
    
    
	char name[1007];
	int subi[4];
	int timei[4];
	int solved;
	int time;
} team[N];
int main() {
    
    
	int n;
	cin >> n;
	for (int i = 0; i < n; ++i) {
    
    
		scanf("%s", team[i].name);
		for (int j = 0; j < 4; ++j) {
    
    
			scanf("%d%d", &team[i].subi[j], &team[i].timei[j]);
			if (team[i].timei[j]) {
    
    
				team[i].solved++;
				team[i].time += team[i].timei[j];
				if (team[i].subi[j] > 1) {
    
    
					team[i].time += ((team[i].subi[j]-1)<<4) +
					                ((team[i].subi[j]-1)<<2);
				}
			}
		}
	}
	node res;
	res.solved = -1;
	res.time = inf;
	for (int i = 0; i < n; ++i) {
    
    
		if ((team[i].solved>res.solved) || (team[i].solved==res.solved &&
		                                    team[i].time<res.time)) {
    
    
			strcpy(res.name, team[i].name);
			res.solved = team[i].solved;
			res.time = team[i].time;
		}
	}
	printf("%s %d %d", res.name, res.solved, res.time);
	return 0;
}

总结

今天的学习让我意识到了控制程序运行时间的重要性,以前从未注意到过,导致我的代码一直又臭又长,因此之后我需要多加思考,让我的代码结构更加简洁,合理。
由于是第一次写博客,希望各位看官多多批评,提点意见,以便学习改正

猜你喜欢

转载自blog.csdn.net/seekerzhz/article/details/112794502