第四章 函数和递归(例题篇)
知识点一:计算组合数 P63
编写函数,参数是两个非负整数n和m,返回组合数
其中m<=n<=25。例如,n=25,m=12时答案为5200300。
#include <stdio.h>
long long factorial(int n)
{
long long m = 1;
for (int i = 1;i <= n;i++)
{
m *= i;
}
return m;
}
long long C(int n, int m)
{
return factorial(n) / (factorial(m)*factorial(n - m));
}
int main()
{
printf("%ld\n", C(21, 1));
return 0;
}
很不幸:n=21,m=1的返回值竟然是-1。(中间结果溢出)
解决方法:约分(n!/m!=(m+1)(m+2)…(n+1)n),利用
的特点。解决溢出的问题。
#include <stdio.h>
long long factorial(int n)
{
long long m = 1;
for (int i = 1;i <= n;i++)
{
m *= i;
}
return m;
}
long long C(int n, int m)
{
if (m < n - m) m = n - m;
long long ans = 1;
for (int i = m + 1;i <= n;i++)
{
ans *= i;
}
for (int i = 1;i <= n - m;i++)
{
ans /= i;
}
return ans;
}
int main()
{
printf("%ld\n", C(21, 1));
return 0;
}
知识点二:素数判定 P64
根据定义,只被1和它自身整除的、大于1的整数称为素数。
#include <stdio.h>
//n太大的请勿调用
int is_prime(int n)
{
if (n <= 1)
return 0;
for (int i = 2;i*i <= n;i++)//只判断不超过sqrt(n)的整数i
{
if (n%i == 0)
{
return 0;
}
}
return 1;
}
int main()
{
printf("%d\n", is_prime(2147483647));
return 0;
}
n过大,i*i会溢出。
改进的版本
#include <stdio.h>
#include<math.h>
int is_prime(int n)
{
if (n <= 1)
return 0;
int m = floor(sqrt(n) + 0.5);
for (int i = 2;i <= m;i++)
{
if (n%i == 0)
{
return 0;
}
}
return 1;
}
int main()
{
printf("%d\n", is_prime(2147483647));
return 0;
}
知识点三:段错误与栈溢出 P77
“段”是指二进制文件内的区域,所有某种特定类型信息被保存在里面。可以用size程序(安装MinGW里面有这个程序)得到可执行文件中各个段的大小。test1.cpp可以编译出test1.exe。
//test1.cpp
#include <stdio.h>
int sum(int *a, int n)//***
{
int ans = 0;
for (int i = 0;i < n;i++)
{
ans += a[i];//***
}
return ans;
}
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sum(a + 1, 3));
return 0;
}
执行size的结果是:
D:>size test1.exe
text data bss dec hex filename
2756 740 224 3720 e88 test1.exe
在可执行文件中,正文段(Text Segment)用于储存指令,数据段(Data Segment)用于储存已初始化的全局变量,BSS段(BSS Segment)用于储存未赋值的全局变量所需的空间。
调用栈并不存储在可执行文件中,而是在运行时创建。调用栈所在的段称为堆栈段(Stack Segment)。和其他段一样,堆栈段也有自己的大小,不能被越界。这种情况叫做栈溢出(Stack Overflow)。
局部变量也是放在堆栈段的,所有建议“把较大的数组放在main函数外”。
例题4-1:古老的密码(Ancient Cipher,UVa1339) P73
给定两个长度一样且不超过100的字符串,判断是否能把其中一个字符串的各个字母重排,之后对26个字母做一个一一映射,使得两个字符串相同。例如,JWPUDJSTVP重排后可以得到WJDUPSJPVT,之后把每个字母映射到它的前面一个字母,得到VICTORIOUS,输入两个字符串,输出YES或者NO。
思路:
因为字母可以重排,每个字母的位置并不重要,重要的是每个字母出现的次数;
①统计两个字符串每个字母出现的次数,得到两个数组cnt1[26],cnt2[26];
②之后我们排序下,排序之后结果相同,说明我们输入的两个字符串就可以通过重排一一映射变得相同了,所以本道题的核心在于【排序】学习笔记:
关于排序
C语言中stdlib.h中又一个叫qsort的库函数,
qsort的声明void qsort(void *base, size_t num, size_t size, int(*cmp)(const void *
, const void *));
如果排序是整型数组的话,
int cmp(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
int cmp(const void *a, const void *b)
{
return *(int *)a - *(int *)b;
}
int main()
{
char a[101], b[101];
int len_s1, len_s2;
while (scanf("%s%s", a, b) != EOF)
{
int len_a, len_b;
len_a = strlen(a);
len_b = strlen(b);
if (len_a != len_b)
{
printf("NO\n");
continue;
}
int cnt1[26], cnt2[26];
memset(cnt1, 0, sizeof(cnt1));
memset(cnt2, 0, sizeof(cnt2));
for (int i = 0;i < len_a;i++)
{
cnt1[a[i] - 'A']++;
cnt2[b[i] - 'B']++;
}
qsort(cnt1, 26, sizeof(cnt1[0]), cmp);
qsort(cnt2, 26, sizeof(cnt2[0]), cmp);
bool flag = true;
for (int i = 0;i < 26;i++)
{
if (cnt1[i] != cnt2[i])
{
flag = false;
break;
}
}
if (flag)
{
printf("YES\n");
}
else
{
printf("NO\n");
}
}
return 0;
}
例题4-2:刽子手游戏(Hangman Judge,UVa489) P79
刽子手游戏其实是一款猜单词游戏,游戏规则是这样的:计算机想一个单词让你猜,你每次可以猜一个字母。如果单词里有那个字母,所有该字母会显示出来;如果没有那个字母,则计算机会在一幅“刽子手”画上填一笔。这幅画一共需要7笔就能完成,因此你最多只能错6次。注意,猜一个已经猜过的字母也算错。
在本题中,你的任务是编写一个“裁判”程 序,输入单词和玩家的猜测,判断玩家赢了 (You win.)、输了(You lose.)还是放弃了 (You chickened out.)。每组数据包含3行,第1行是游戏编号(-1为输入结束标记),第2行是计算机想的单词,第3行是玩家的猜测。后两行保证只含小写字母。
样例输入:
1
cheese
chese
2
cheese
abcdefg
3
cheese
abcdefgij
-1
样例输出:
Round 1
You win.
Round 2
You chickened out.
Round 3
You lose.
#include <stdio.h>
#include<string.h>
#define maxn 100
int main()
{
freopen("input.txt", "rb", stdin);
int firstLine;
char word[maxn], geuss[maxn];
int len_word, len_geuss;
int Round = 0;
while (scanf("%d%s%s", &firstLine, word, geuss) == 3 && firstLine != -1)
{
len_word = strlen(word);
len_geuss = strlen(geuss);
int count = len_word;
int restCount = 6;
for (int i = 0;i < len_geuss;i++)
{
int old_count = count;
for (int j = 0;j < len_word;j++)
{
if (word[j] == geuss[i])
{
word[j] = '\0';
count--;
}
}
if (old_count == count)
{
restCount--;
if (restCount < 0)
{
printf("Round %d\nYou lose.\n", ++Round);
break;
}
}
if (count == 0)
{
printf("Round %d\nYou win.\n", ++Round);
break;
}
}
if (count > 0 && restCount >= 0)
{
printf("Round %d\nYou chickened out.\n", ++Round);
}
}
return 0;
}
例题4-3:救济金发放(The Dole Queue,UVa133) P82
n(n<20)个人站成一圈,逆时针编号为1~n。有两个官员,A从1开始逆时针数,B从n开始顺时针数。在每一轮中,官员A数k个就停下来,官员B数m个就停下来(注意有可能两个官员停在同一个人上)。接下来被官员选中的人(1个或者2个)离开队伍。
输入n,k,m输出每轮里被选中的人的编号(如果有两个人,先输出被A选中的)。例如,n=10,k=4,m=3,输出为4 8, 9 5, 3 1, 2 6, 10, 7。注意:输出的每个数应当恰好占3列。
#include <stdio.h>
#define maxn 100
int main()
{
/*freopen("input.txt", "rb", stdin);*/
int n, k, m;
if (scanf("%d%d%d", &n, &k, &m) != 3 || n <= 0)
{
return 0;
}
int a[maxn];
for (int i = 0;i < n;i++)
{
a[i] = i + 1;
}
int left = n;
int p = -1, q = n;
while (left)
{
int pn = k, qn = m;
while (pn)
{
p = (p + 1) % n;
if (a[p] != 0)
{
pn--;
}
}
while (qn)
{
q = (q + n - 1) % n;
if (a[q] != 0)
{
qn--;
}
}
printf("%d", a[p]);
left -= 1;
if (q != p)
{
printf(" %d", a[q]);
a[p] = 0;
left -= 1;
}
a[p] = a[q] = 0;
if (left)
{
printf(",");
}
}
printf("\n");
return 0;
}
例题4-4:信息解码(Message Decoding,UVa213) P83
考虑下面的01串序列:
0, 00, 01, 10, 000, 001, 010, 011, 100, 101, 110, 0000, 0001, …, 1101, 1110, 00000, …
首先是长度为1的串,然后是长度为2的串,依此类推。如果看成二进制,相同长度的后 一个串等于前一个串加1。注意上述序列中不存在全为1的串。
你的任务是编写一个解码程序。首先输入一个编码头(例如AB#TANCnrtXc),则上述序列的每个串依次对应编码头的每个字符。例如,0对应A,00对应B,01对应#,…,110对应X,0000对应c。接下来是编码文本(可能由多行组成,你应当把它们拼成一个长长的01 串)。编码文本由多个小节组成,每个小节的前3个数字代表小节中每个编码的长度(用二进制表示,例如010代表长度为2),然后是各个字符的编码,以全1结束(例如,编码长度为2的小节以11结束)。编码文本以编码长度为000的小节结束。
例如,编码头为$#**\,编码文本为0100000101101100011100101000,应这样解码:010(编码长度为2)00(#)00(#)10(*)11(小节结束)011(编码长度为3)000(\)111(小节结束)001(编码长度为1)0($)1(小节结束)000(编码结束)。
输入:
$#**\
0100000101101100011100101000
AB#TANCnrtXc
011110010101111001010100100001011000
输出:
##*\$
XCtA#BBT
#include <stdio.h>
#include <string.h>
int readchar()
{
for (;;)
{
int ch = getchar();
if (ch != '\n'&&ch != '\r')
{
return ch;
}
}
}
int readint(int c)
{
int v = 0;
while (c--)
{
v = v * 2 + readchar() - '0';
}
return v;
}
int code[8][1 << 8];
int readcodes()
{
memset(code, 0, sizeof(code));
code[1][0] = readchar();
for (int len = 2;len <= 7;len++)
{
for (int i = 0;i < (1 << len) - 1;i++)
{
int ch = getchar();
if (ch == EOF) return 0;
if (ch == '\n' || ch == '\r')return 1;
code[len][i] = ch;
}
}
return 1;
}
void printcodes()
{
for (int len = 1;len <= 7;len++)
{
for (int i = 0;i < (1 << len) - 1;i++)
{
if (code[len][i] == 0)
return;
printf("code[%d][%d]=%c\n", len, i, code[len][i]);
}
}
}
int main()
{
freopen("input.txt", "rb", stdin);
while (readcodes())
{
/*printcodes();*/
for (;;)
{
int len = readint(3);
if (len == 0)
break;
/*printf("len=%d\n", len);*/
for (;;)
{
int v = readint(len);
/* printf("v=%d\n", v);*/
if (v == (1 << len) - 1)
break;
putchar(code[len][v]);
}
}
putchar('\n');
}
return 0;
}
例题4-5:追踪电子表格中的单元格(Spreadsheet Tracking,UVa512) P85
题目:
https://blog.csdn.net/weixin_43899069/article/details/104370498
输入:
7 9
5
DR 2 1 5
DC 4 3 6 7 9
IC 1 3
IR 2 2 4
EX 1 2 6 5
4
4 8
5 5
7 8
6 5
0 0
输出:
Spreadsheet #1
Cell data in (4,8)moved to (4,6)
Cell data in (5,5)GONE
Cell data in (7,8)GONE
Cell data in (6,5)moved to (1,2)
答案一:
#include <stdio.h>
#include<string.h>
#define maxd 100
#define BIG 10000
int r, c, n, d[maxd][maxd], d2[maxd][maxd], ans[maxd][maxd], cols[maxd];
void copy(char type, int p, int q)
{
if (type == 'R')
{
for (int i = 1;i <= c;i++)
{
d[p][i] = d2[q][i];
}
}
else
{
for (int i = 1;i < r;i++)
{
d[i][p] = d2[i][q];
}
}
}
void del(char type)
{
memcpy(d2, d, sizeof(d));
int cnt = type == 'R' ? r : c, cnt2 = 0;
for (int i = 1;i <= cnt;i++)
{
if (!cols[i])
{
copy(type, ++cnt2, i);
}
}
if (type == 'R')
{
r = cnt2;
}
else
{
c = cnt2;
}
}
void ins(char type)
{
memcpy(d2, d, sizeof(d));
int cnt = type == 'R' ? r : c, cnt2 = 0;
for (int i = 1;i <= cnt;i++)
{
if (cols[i])
{
copy(type, ++cnt2, 0);
}
copy(type, ++cnt2, i);
}
if (type == 'R')
{
r = cnt2;
}
else
{
c = cnt2;
}
}
int main()
{
freopen("input.txt", "rb", stdin);
freopen("output.txt", "wb", stdout);
int r1, c1, r2, c2, q, kase = 0;
char cmd[10];
memset(d, 0, sizeof(d));
while (scanf("%d%d%d", &r, &c, &n) == 3 && r)
{
int r0 = r, c0 = c;
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= c;j++)
{
d[i][j] = i*BIG + j;
}
}
while (n--)
{
scanf("%s", cmd);
if (cmd[0] == 'E')
{
scanf("%d%d%d%d", &r1, &c1, &r2, &c2);
int t = d[r1][c1];d[r1][c1] = d[r2][c2];d[r2][c2] = t;
}
else
{
int a, x;
scanf("%d", &a);
memset(cols, 0, sizeof(cols));
for (int i = 0;i < a;i++)
{
scanf("%d", &x);
cols[x] = 1;
}
if (cmd[0] == 'D')
{
del(cmd[1]);
}
else
{
ins(cmd[1]);
}
}
}
//后面的代码是查询上面的操作之前表里的数据现在表的那了。
memset(ans, 0, sizeof(ans));
for (int i = 1;i <= r;i++)
{
for (int j = 1;j <= c;j++)
{
ans[d[i][j] / BIG][d[i][j] % BIG] = i*BIG + j;
}
}
if (kase)
{
printf("\r\n");
}
printf("Spreadsheet #%d\r\n", ++kase);
scanf("%d", &q);
while (q--)
{
scanf("%d%d", &r1, &c1);
printf("Cell data in (%d,%d)", r1, c1);
if (ans[r1][c1] == 0)
{
printf("GONE\r\n");
}
else
{
printf("moved to (%d,%d)\r\n", ans[r1][c1] / BIG, ans[r1][c1] % BIG);
}
}
}
return 0;
}
答案二:
#include <stdio.h>
#define maxd 10000
struct Command {
char c[5];
int r1, c1, r2, c2;
int a, x[20];
}cmd[maxd];
int r, c, n;
int simulate(int *r0, int *c0)
{
for (int i = 0;i < n;i++)
{
if (cmd[i].c[0] == 'E')
{
if (cmd[i].r1 == *r0&&cmd[i].c1 == *c0)
{
*r0 = cmd[i].r2;
*c0 = cmd[i].c2;
}
else if (cmd[i].r2 == *r0&&cmd[i].c2 == *c0)
{
*r0 = cmd[i].r1;
*c0 = cmd[i].c1;
}
}
else
{
int dr = 0, dc = 0;
for (int j = 0;j < cmd[i].a;j++)
{
int x = cmd[i].x[j];
if (cmd[i].c[0] == 'I')
{
if (cmd[i].c[1] == 'R'&&x <= *r0)
{
dr++;
}
if (cmd[i].c[1] == 'C'&&x <= *c0)
{
dc++;
}
}
else
{
if (cmd[i].c[1] == 'R'&&x == *r0)
return 0;
if (cmd[i].c[1] == 'C'&&x == *c0)
return 0;
if (cmd[i].c[1] == 'R'&&x < *r0)
dr--;
if (cmd[i].c[1] == 'C'&&x < *c0)
dc--;
}
}
*r0 += dr;*c0 += dc;
}
}
}
int main()
{
freopen("input.txt", "rb", stdin);
freopen("output.txt", "wb", stdout);
int r0, c0, q, kase = 0;
while (scanf("%d%d%d", &r, &c, &n) == 3 && r)
{
for (int i = 0;i < n;i++)
{
scanf("%s", cmd[i].c);
if (cmd[i].c[0] == 'E')
{
scanf("%d%d%d%d", &cmd[i].r1, &cmd[i].c1, &cmd[i].r2, &cmd[i].c2);
}
else
{
scanf("%d", &cmd[i].a);
for (int j = 0;j < cmd[i].a;j++)
{
scanf("%d", &cmd[i].x[j]);
}
}
}
if (kase > 0)
printf("\r\n");
printf("Spreadsheet #%d\r\n", ++kase);
scanf("%d", &q);
while (q--)
{
scanf("%d%d", &r0, &c0);
printf("Cell data in (%d,%d)", r0, c0);
if (!simulate(&r0, &c0))
printf("GONE\r\n");
else
printf("moved to (%d %d)\r\n", r0, c0);
}
}
return 0;
}
例题4-6:师兄帮帮忙(A Typical Homework,UVa12412) P89
题目:
https://vjudge.net/problem/UVA-12412
#include <stdio.h>
#include<string.h>
#define maxn 1000
#define maxl 100
#define EPS 1e-5
int n = 0;
char sid[maxn][maxl];
int cid[maxn];
char name[maxn][maxl];
int score[maxn][5];
int removed[maxn];
const char *course_name[] = { "Chinese","Mathematics","English","Programming" };
int valid(int k)
{
for (int i = 0;i < k;i++)
{
if (!removed[i])
{
if (strcmp(sid[i], sid[k]) == 0)
{
return 0;
}
}
}
return 1;
}
void add()
{
for (;;)
{
printf("Please enter the SID,CID,name and four scores.Enter 0 to finish.\n");
scanf("%s", sid[n]);
if (strcmp(sid[n], "0") == 0)
break;
scanf("%d%s%d%d%d%d", &cid[n], name[n], &score[n][0], &score[n][1], &score[n][2], &score[n][3]);
if (valid(n))
{
score[n][4] = score[n][0] + score[n][1] + score[n][2] + score[n][3];
n++;
}
else
{
printf("Duplicated SID.\n");
}
}
}
int rank(int k)
{
int r = 0;
for (int i = 0;i < n;i++)
{
if (!removed[i] && score[i][4]>score[k][4])
k++;
}
return r + 1;
}
void DQ(int isq)
{
char s[maxl];
for (;;)
{
printf("Please enter SID or name.Enter 0 to finish.\n");
scanf("%s", s);
if (strcmp(s, "0") == 0)break;
int r = 0;
for (int i = 0;i < n;i++)
{
if (!removed[i])
{
if (strcmp(sid[i], s) == 0 || strcmp(name[i], s) == 0)
{
if (isq)
{
printf("%d %s %d %s %d %d %d %d %d %.2f\n", rank(i), sid[i], cid[i], name[i], score[i][0], score[i][1], score[i][2], score[i][3], score[i][4], score[i][4] / 4.0 + EPS);
}
else
{
removed[i] = 1;r++;
}
}
}
}
if (!isq)
printf("%d student(s) removed.\n", r);
}
}
double get_course_stat(int c, int s, int *passed, int *failed)
{
int tot = 0;
*passed = *failed = 0;
for (int i = 0;i < n;i++)
{
if (!removed[i] && (c == 0 || cid[i] == c))
{
tot += score[i][s];
if (score[i][s] >= 60)
(*passed)++;
else
(*failed)++;
}
}
return (double)tot / (double)(*passed + *failed);
}
void get_overall_stat(int c, int *cnt)
{
cnt[0] = cnt[1] = cnt[2] = cnt[3] = cnt[4] = 0;
for (int i = 0;i < n;i++)
{
if (!removed[i] && (c == 0 || cid[i] == c))
{
int k = 0;
for (int j = 0;j < 4;j++)
{
if (score[i][j] >= 60)
{
k++;
}
}
cnt[k]++;
}
}
}
void stat()
{
int c;
printf("Please enter class ID,0 for the whole statistics.\n");
scanf("%d", &c);
for (int i = 0;i < 4;i++)
{
int passed, failed;
double avg = get_course_stat(c, i, &passed, &failed);
printf("%s\n", course_name[i]);
printf("Average Score:%.2f\n", avg + EPS);
printf("Number of passed students:%d\n", passed);
printf("Number of failed students:%d\n", failed);
printf("\n");
}
int cnt[5];
get_overall_stat(c, cnt);
printf("Overall:\n");
printf("Number of student who passed all subjects:%d\n", cnt[4]);
printf("Number of student who passed 3 or more subjects:%d\n", cnt[4] + cnt[3]);
printf("Number of student who passed 2 or more subjects:%d\n", cnt[4] + cnt[3] + cnt[2]);
printf("Number of student who passed 1 or more subjects:%d\n", cnt[4] + cnt[3] + cnt[2] + cnt[1]);
printf("Number of student who failed all subjects:%d\n", cnt[0]);
printf("\n");
}
int main()
{
/*freopen("input.txt", "rb", stdin);*/
for (;;)
{
int choice;
printf("Welcome to Student Performance Management System (SPMS).\n");
printf("\n");
printf("1 - Add\n");
printf("2 - Remove\n");
printf("3 - Query\n");
printf("4 - Show ranking\n");
printf("5 - Show Statistics\n");
printf("0 - Exit\n");
printf("\n");
scanf("%d", &choice);
if (choice == 0)break;
if (choice == 1)add();
if (choice == 2)DQ(0);
if (choice == 3)DQ(1);
if (choice == 4)printf("Showing the ranklist hurts students' self-esteem.Don't do that.\n");
if (choice == 5)stat();
}
return 0;
}