算法竞赛入门经典学习--第4章(函数和递归)

例4.2 刽子手游戏

题目

刽子手游戏其实是一款猜单词游戏,如图4- 1所示。游戏规则是这样的:计算机想一个单词 让你猜,你每次可以猜一个字母。如果单词里有那个字母,所有该字母会显示出来;如果没有那 个字母,则计算机会在一幅“刽子手”画上填一 笔。这幅画一共需要7笔就能完成,因此你最多 只能错6次。注意,猜一个已经猜过的字母也算错。在本题中,你的任务是编写一个“裁判”程序,输入单词和玩家的猜测,判断玩家赢了 (You win.)、输了(You lose.)还是放弃了 (You chickened out.)。每组数据包含3行,第1 行是游戏编号(-1为输入结束标记),第2行是 计算机想的单词,第3行是玩家的猜测。后两行 保证只含小写字母。
测试样例:
1cheese chese
2cheese abcdefg
3cheese abcdefgij
1样例输出:
Round 1 You win.
Round 2 You chickened out.
Round 3 You lose.

分析

该题目逐个拿猜测字符和整个正确字符对照,判断是否正确,知道需要猜测的字符数为0(win)或者最大猜测次数为0(lose)结束游戏。

代码

使用vs编程,学习了以下注意事项:
1.用scanf_s 而不是scanf
2.scanf_s("%s",s,length(s)) //字符串输入方法
3.数组做传递参数
void guess(char ch,char s[]);

完整代码如下:

#include<stdio.h>
#include<string.h> 
#define maxn 100 
int left, chance; //还需要猜left个位置,错chance次之后就会输 
char s[maxn], s2[maxn]; 
				  //答案是字符串s,玩家猜的字母序列是s2 
int win, lose; 
				  //win=1表示已经赢了;lose=1表示已经输了 
 
void guess(char ch,char s[])
{
	int bad = 1;
	for (int i = 0; i < strlen(s); i++)
		if (s[i] == ch) { left--; s[i] = ' '; bad = 0; }
	if (bad) --chance;
	if (!chance) lose = 1;
	if (!left) win = 1;
}


int main() { 
	int rnd;

while(1) 
{ 
	scanf_s("%d", &rnd);
	scanf_s("%s", s,maxn);
	scanf_s("%s", s2,maxn);
	
	if (rnd == -1)
		break;
	
	printf("Round %d\n", rnd);
	win = lose = 0; //求解一组新数据之前要初始化 
	left = strlen(s); 
	chance = 7; 
	for(int i = 0; i < strlen(s2); i++) 
	{ 
		guess(s2[i],s); //猜一个字母 
		if(win || lose) break; //检查状态 
	}//根据结果进行输出 
	if(win) printf("You win.\n"); 
	else if(lose) printf("You lose.\n"); 
	else printf("You chickened out.\n"); 
}
return 0; 
}

例4-3 救济金发放

题目

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列。

分析

对每个人进行标号,同时定义他们的状态:是否在队伍中
每次循环即为官员行走一次(go函数),结束后判断站位,减去1~2人
自己站的那个位置也要数

代码
#include<stdio.h> 
#define maxn 25 
int n, k, m, a[maxn];

int go(int p, int d, int t) {
	while (t--) {
		do { p = (p + d + n - 1) % n + 1; } 
		while (a[p] == 0); //走到下一个非0数字 
	}return p; 
}

int main() {
	while (scanf_s("%d%d%d", &n, &k, &m) == 3 && n) {
		for (int i = 1; i <= n; i++) 
			a[i] = i; 
		int left = n; //还剩下的人数 
		int p1 = n, p2 = 1;
		while(left) { p1 = go(p1, 1, k);
		p2 = go(p2, -1, m);
		printf("%3d", p1);
		left--; 
		if(p2 != p1) { 
			printf("%3d", p2); 
			left--; 
		} 
		a[p1] = a[p2] = 0; 
		if(left) printf(","); 
		}
		printf("\n");
	}return 0;
}

例4-5电子表格模拟

题目

你的任务是模拟这样的n个操作。具体来说一共有5种操作:
EX r1 c1 r2 c2交换单元格(r1,c1),(r2,c2)。
A x1 x2 … xA 插入或删除A行或列(DC-删除列,DR-删除行,IC-插入 列,IR-插入行,1≤A≤10)。
在插入/删除指令后,各个x值不同,且顺序任意。
接下来是q个查询,每个查询格式 为“r c”,表示查询原始表格的单元格(r,c)。
对于每个查询,输出操作执行完后该单元格的新位置。输入保证在任意时刻行列数均不超过50。

分析

此题目需满足要求为:交换两个任意元素,插入删除任意位置行列
模拟操作,算出最后的电子表格,然后在每次查询时直接在电子 表格中找到所求的单元格。

memset:将一块内存初始化为指定值

代码
#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() {
	int r1, c1, r2, c2, q, kase = 0; 
	char cmd[10]; 
	memset(d, 0, sizeof(d)); 
	while (scanf_s("%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("%s", cmd,10); //cmd为操作指令集 
			if (cmd[0] == 'E') {   //交换
				scanf_s("%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_s("%d", &a); 
				memset(cols, 0, sizeof(cols)); 
				for (int i = 0; i < a; i++) { 
					scanf_s("%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; //这什么BIG是啥
		} 
		if (kase > 0) printf("\n");
		printf("Spreadsheet #%d\n", ++kase); 
		scanf_s("%d", &q); 
		while (q--) {
			scanf_s("%d%d", &r1, &c1); 
			printf("Cell data in (%d,%d) ", r1, c1); 
			if (ans[r1][c1] == 0) printf("GONE\n");
			else 
				printf("moved to (%d,%d)\n", ans[r1][c1] / BIG, ans[r1][c1] % BIG); 
		}
	}
	return 0;
}

习题4-2 正方形

题目

有n行n列(2≤n≤9)的小黑点,还有m条线段连接其中的一些黑点。统计这些线段连成 了多少个正方形(每种边长分别统计)。 行从上到下编号为1~n,列从左到右编号为1~n。边用Hij和Vij表示,分别代表边 (i,j)-(i,j+1)和(i,j)-(i+1,j)。
如图4-5所示最左边的线段用V11表示。图中包含两个边长为1的正方形和一个边长为2的正方形。

分析

H为竖线、V为横线,正方形至多三种边长1、2、3
暴力求解

代码

代码如下,由于输入字符不知为何总是报错,故拿二维数组存储边信息

#include<stdio.h>
#include<string.h>
int n, m, size[10], h[15][15], v[15][15];//h是横边,v是竖边


void findsquare()
{
	int flag;
	for (int len = 1; len < n; len++) { //搜索边长
		for (int i = 0; i < n - len; i++) {
			for (int j = 0; j < n - len; j++) {
				flag = 1;
				for (int x = i; x < i + len; x++) {
					if (v[x][j] == 1 && flag == 1)
						flag = 1;
					else flag = 0;						
				}
				for (int y = j; y < j + len; y++) {
					if (h[i][y] == 1 && flag == 1)
						flag = 1;
					else flag = 0;
				}
				if (flag == 1)
					size[len - 1]++;
			}
		}
	}

}


int main()
{
	scanf_s("%d%d", &n, &m);

		//n行列,m条线段
		memset(size, 0, sizeof(size));
		for (int i = 0;i<n; i++) {
			for (int j = 0;j<n; j++) {
				scanf_s("%d", &h[i][j]);				
			}
}

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n; j++) {
				scanf_s("%d", &v[i][j]);
			}
		}

		//for (int i = 0; i < m; i++)
		//{
		//	char type;
		//	scanf_s("%c", &type,1);
		//	int x, y;
		//	scanf_s("%d%d",&x, &y);
		//	if (type == 'H')
		//		h[x][y] = 1;//竖线
		//	else if (type == 'V')
		//		v[x][y] = 1;//横线
		//}//键入所有边信息
		
		findsquare();
		for (int k = 0; k < 10; k++)
			printf("%d ",size[k]);
	
	return 0;
}

习题4-8 特别困的学生

题目

在这里插入图片描述

分析

每个学生按醒睡规律循环,当醒周期刚结束时,判断人数比,是否睡觉。

直接模拟每个时刻然后判断这个时刻是否有小于一半的人在睡觉
如果有超过一半没有睡眠那么则循环到最后一个睡醒的位置输出结果
如果在进行时发现当前的状态等于最开始的状态就表示出现了周期循环,那么则无解

代码

转载:解法

#include <stdio.h>
#include <stdlib.h>
//暴力模拟1000min
struct stu
{
	int awa, sle, ing;
};

int judge(struct stu peo[], int slepnum, int n, int t)
{
	if (t == 1001) return -1;
	int i;
	for (i = 0; i < n; i++)//更新下一分钟每位同学应该处于的状态
	{
		if (peo[i].ing == peo[i].awa&&slepnum < n / 2 + 1)
			peo[i].ing = 1;
		else
			peo[i].ing++;
		if (peo[i].ing > peo[i].awa + peo[i].sle)
			peo[i].ing = 1;
	}
	slepnum = 0;
	for (i = 0; i < n; i++)//更新当前睡觉的人数
		if (peo[i].ing > peo[i].awa)
			slepnum++;
	if (!slepnum) return t;
	judge(peo, slepnum, n, ++t);
}

int main()
{
	struct stu peo[10];
	int i, n, t = 0;
	while (scanf_s("%d", &n) && n)
	{
		int slepnum = 0;      //当前睡觉的人数
		for (i = 0; i < n; i++)
		{
			scanf_s("%d%d%d", &peo[i].awa, &peo[i].sle, &peo[i].ing);
			if (peo[i].ing > peo[i].awa)
				slepnum++;
		}
		if (slepnum) printf("Case %d: %d\n", ++t, judge(peo, slepnum, n, 2));
		else printf("Case %d: 1\n", ++t);
	}
	return 0;
}

习题4-4 筛子涂色

题目

给两个骰子,每枚骰子每个面由红(red),蓝(blue),绿(green)三种颜色之一染色,可以把每枚骰子按照图示方式编号,然后按照编号给出颜色顺序,问这两枚骰子能否通过旋转变成展示的颜色相对位置一致?

分析

不管筛子的旋转,对三对颜色做匹配,全部成功,则等价

代码
#include<stdio.h>
#include<string.h>

int main() {
	int a[6], b[6], flag1[3], flag2[3],p1[3],p2[3];
	int count;
	memset(flag1, 0, 3);
	memset(flag2, 0, 3);
	//以123记录rgb,对应面和相同(22/13单独讨论)则面一样
	for (int i = 0; i < 6; i++)
		scanf_s("%d", &a[i]);
	for (int i = 0; i < 6; i++)
		scanf_s("%d", &b[i]);
	p1[0] = a[0] + a[5];
	p1[1] = a[1] + a[4];
	p1[2] = a[2] + a[3];
	p2[0] = b[0] + b[5];
	p2[1] = b[1] + b[4];
	p2[2] = b[2] + b[3];
	if (a[0] == 2 && a[5] == 2)
		flag1[0] = 1;
	if (a[1] == 2 && a[4] == 2)
		flag1[0] = 1;
	if (a[2] == 2 && a[3] == 2)
		flag1[0] = 1;
	if (b[0] == 2 && b[5] == 2)
		flag2[0] = 1;
	if (b[1] == 2 && b[4] == 2)
		flag2[0] = 1;
	if (b[2] == 2 && b[3] == 2)
		flag2[0] = 1;
	count = 0;
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			if (p1[i] == p2[j]) {
				if (p1[i] == 4) {
					if (flag1[i] == flag2[j])
						count++;
					else	continue;
				}
				else   count++;
			}
			else continue;

		}
	}

	if (count == 3)
		printf("筛子等价");
	else
		printf("筛子不等价");
	
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值