算法竞赛入门经典(第二版)第三章习题

声明:作者水平有限,只是会基础C语言的小菜,C++还未入门。作者仅根据算法竞赛入门经典(第二版)书上第三章习题所述题意而编写,并未严格按照原题的输入输出编写,代码仅经过个人测试(OJ网站太慢了)。代码、文章排版及书写如有错误,欢迎指正!

目录

声明:作者水平有限,只是会C语言的小菜,C++还未入门。作者仅根据算法竞赛入门经典(第二版)书上第三章习题所述题意而编写,并未严格按照原题的输入输出编写,程序仅经过个人测试,如有错误,欢迎指正!

习题3-1 得分(Score,ACM/ICPC Seoul 2005,UVa1585)

习题3-2 分子量(Molar Mass,ACM/ICPC Seoul 2007,UVa1586)

习题3-3 数数字(Digit Counting,ACM/ICPC Danang 2007,UVa1225)

习题3-4 周期串(Periodic Strings,UVa455)

习题3-5 谜题(Puzzle,ACM/ICPC World Finals 1993,UVa227)

习题3-6 纵横字谜的答案(Crossword Answers,ACM/ICPC World Finals 1994,UVa232)

习题3-7 DNA序列(DNA Consensus String,ACM/ICPC Seoul 2006,UVa1368)

习题3-8 循环小数(Repeating Decimals,ACM/ICPC World Finals 1990,UVa202)

习题3-9 子序列(All in All,UVa10340) 

习题3-10 盒子(Box,ACM/ICPC NEERC 2004,UVa1587)

习题3-11 换抵挡装置(Kickdown,ACM/ICPC NEERC 2006,UVa1588)

习题3-12  浮点数(Floating-Point Numbers,UVa11809)  


习题3-1 得分(Score,ACM/ICPC Seoul 2005,UVa1585)

        给出一个由O和X组成的串(长度为1~80),统计得分。每个O的得分为目前连续出现的O的个数,X的得分为0。例如,OOXXOXXOOO的得分为1+2+0+0+1+0+0+1+2+3。

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

int main() 
{
	char ox[100];
	while(scanf("%s", ox) == 1)
	{
		int sum = 0, t = 0;
		for(int i = 0; i < strlen(ox); i++)
		{
			if(ox[i] == 'O')
				t++;
			else if(ox[i] == 'X')
				t = 0;
			sum += t;
		}
		printf("%d\n", sum);
	}
	return 0;
} 

习题3-2 分子量(Molar Mass,ACM/ICPC Seoul 2007,UVa1586)

        给出一种物质的分子式(不带括号),求分子量。本题中的分子式只包含4种原子,分别为C,H,O,N,原子量分贝为12.01,1.008,16.00,14.01(单位:g/mol)。例如,C6H5OH的分子量为94.108g/mol。

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.h>

#define c 12.01
#define h 1.008
#define o 16.00
#define n 14.01

int i;	//全局变量 

//判断元素数字下标 
int subscript(char *a)
{
	int t = 0, s = 0;
	while(isdigit(a[++i]))	//判断元素符号后面的数字个数 
		t++;
	if(t == 0)
		return 1;
	for(; t > 0; t--)
		s += (a[i-t] - '0')*pow(10,t-1);	//转化 
	return s;
} 

int main() 
{
	char mf[100];
	while(scanf("%s", mf) == 1)
	{
		float sum = 0;
		for(i = 0; i < strlen(mf);)
		{
			switch(mf[i])
           		{
               		case 'C':sum += c*subscript(mf);
                    	break;
                	case 'H':sum += h*subscript(mf);
                    	break;
                	case 'O':sum += o*subscript(mf);
                    	break;
                	case 'N':sum += n*subscript(mf);
                    	break;
            	}
		}
		printf("%.3fg/mol\n", sum);	//保留三位小数 
	}
	return 0;
} 

注意:个人认为分子式中元素符号后有多位数的情况不能忽略。

习题3-3 数数字(Digit Counting,ACM/ICPC Danang 2007,UVa1225

        把前n(n≤10000)个整数顺次写在一起:123456789101112...数一数0~9各出现多少次(输出10个整数,分别是0,1, ... ,9出现的次数)。

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

int main() 
{
	char a[10000];
	while(scanf("%s", a) == 1)
	{
		int num[10] = {};	//数组初始化 
		for(int i = 0; i < strlen(a); i++)
			num[a[i] - '0']++;	//对应数组项计数 
		for(int i = 0; i < 10; i++)
			printf("%5d", num[i]);
		printf("\n");
	}
	return 0;
} 

习题3-4 周期串(Periodic Strings,UVa455)

        如果一个字符串可以由某个长度为k的字符串重复多次得到,则称该串以k为周期。例如,abcabcabcabc以3为周期(注意,它也以6和12为周期)。

        输入一个长度不超过80的字符串,输出其最小周期。

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

int main() 
{ 
	char string1[100]; 
	while(scanf("%s", string1) == 1)
	{
   		char string2[85]; 		
		for(int k = 1; k <= strlen(string1); k++)
		{
   			strncpy(string2,string1,k); 	//从首部开始截取字符串,长度为k
			int i = 0;
			for(; i < strlen(string1); i++)
				if(string1[i] != string2[i % k])	//遍历字符串判断是否周期循环 
					break;	
			if(i >= strlen(string1) && strlen(string1) % k == 0)
			{
				//printf("%s\n", string2);
				printf("%d\n",k);
				break;
			}
		}
   	}
   return 0; 
} 

注:C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。

习题3-5 谜题(Puzzle,ACM/ICPC World Finals 1993,UVa227)

        有一个5*5的网格,其中恰好有一个格子是空的,其他格子各有一个字母。一共有4种指令:A,B,L,R,分别表示把空格上、下、左右的相邻字母移到空格种。输入初始网格和指令序列(以数字0结束),输出指令执行完毕后的网格。如果有非法指令,应输出“This puzzle has no final configuration.”,例如,图3-5种执行ARRBBL0后,效果如图3-6所示。

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

int main()
{
	char a[30];
	scanf("%[^\n]", a);	//读取除回车以外的字符 
	fflush(stdin);	//清空输入缓冲区,确保不影响后面的数据读取 
	char grid[5][5];
	for(int i = 0, k = 0; i < 5; i++)
		for(int j = 0; j < 5; j++)	
			grid[i][j] = a[k++];
	/*for(int i = 0; i < 5; i++)	//打印输入网格 
	{
		for(int j = 0; j < 5; j++)	
			printf("%c", grid[i][j]);
		printf("\n");
	}*/
	char *ptr;
	int x, y;
	ptr = strchr(a, ' ');	//寻找空格位置
	x = (ptr - a) / 5, y = (ptr - a) % 5;	
	//printf("空格的坐标为(%d,%d)\n", x, y);
	char cmd[100];
	scanf("%[^0]", cmd);	//读取除'0'以外的字符 
	//printf("%s\n", cmd);
	for(int i = 0; i < strlen(cmd); i++)
	{
		switch(cmd[i])
		{
			case 'A':
				grid[x][y] = grid[x - 1][y];
				grid[x - 1][y] = ' ';
				x = x - 1;
				break;
			case 'B':
				grid[x][y] = grid[x + 1][y];
				grid[x + 1][y] = ' ';
				x = x + 1;
				break;
			case 'L':
				grid[x][y] = grid[x][y - 1];
				grid[x][y - 1] = ' ';
				y = y - 1;
				break;	
			case 'R':
				grid[x][y] = grid[x][y + 1];
				grid[x][y + 1] = ' ';
				y = y + 1;
				break;
			default:
				printf("This puzzle has no final configuration.\n");
				return 0;
		}
	}
	for(int i = 0; i < 5; i++)
	{
		for(int j = 0; j < 5; j++)	
			printf("%c", grid[i][j]);
		printf("\n");
	}		
	return 0;
}

习题3-6 纵横字谜的答案(Crossword Answers,ACM/ICPC World Finals 1994,UVa232)

       输入一个r行c列(1≤r,c≤10)的网格,黑格用“*”表示,每个白格都填有一个字母。如果一个白格的左边相邻位置或者上边相邻位置没有白格(可能是黑格,也可能出了网格边界),则称这个白格是一个起始格。

        首先把所有起始格按照从上到下、从左到右的顺序编号为1, 2, 3,…,如图3-7所示。

         接下来要找出所有横向单词(Across)。这些单词必须从一个起始格开始,向右延伸到一个黑格的左边或者整个网格的最右列。最后找出所有竖向单词(Down)。这些单词必须从一个起始格开始,向下延伸到一个黑格的上边或者整个网格的最下行。输入输出格式和样例请参考原题。

 (哈哈,题目没怎么看懂,也不想看懂,找了个其他作者写的代码)请参考:

#include<stdio.h>
#include<string.h>
#define m 105
char s[m][m];
int sta[m][m];
int main(){
	int r,c,kase=0;
	while(scanf("%d%d\n",&r,&c)==2&&r){
		memset(sta,0,sizeof(sta));
		int pos=1;
		for(int i=0;i<r;i++){
			for(int j=0;j<c;j++){
				scanf("%c",&s[i][j]);
				if(s[i][j]=='*')continue;
				if(j<1||s[i][j-1]=='*'||i<1||s[i-1][j]=='*'){
					sta[i][j]=pos;
					pos++;
				}
			}
			getchar();
		}
		kase++;
		if(kase>1)printf("\n");
		printf("puzzle #%d:\n",kase);
		printf("Across\n");
		for(int i=0;i<r;i++){
			int j=0;
			while(j<c){
				if(s[i][j]=='*'||sta[i][j]==0){
					j++;
					continue;
					
				}
				printf("%3d.%c",sta[i][j],s[i][j]);
				j++;
				while(s[i][j]!='*'&&j<c){
					printf("%c",s[i][j]);
					j++;
				}
				printf("\n");
			}
		}
		printf("Down\n");
		for(int i=0;i<r;i++){
			for(int j=0;j<c;j++){
					if(s[i][j]=='*'||sta[i][j]==0)continue;
					printf("%3d.%c",sta[i][j],s[i][j]);
					sta[i][j]=0;
					int k=i+1;
					while(k<r&&s[k][j]!='*'){
						printf("%c",s[k][j]);
						sta[k][j]=0;
						k++;
					}
					printf("\n");	
				}
			}	
	}
	return 0;
} 

注:原文链接:算法竞赛入门经典(第二版)习题答案——第三章3.1-3.6_qq_45911039的博客-CSDN博客

习题3-7 DNA序列(DNA Consensus String,ACM/ICPC Seoul 2006,UVa1368)

         输入m个长度均为n的DNA序列,求一个DNA序列,到所有序列的总Hamming距离尽量小。两个等长祖父穿的Hamming距离等于字符不同的位置个数,例如,ACGT和GCGA的Hamming距离为2(左数第1,4个字符不同)。

        输入整数m和n(4≤m≤50,4≤n≤1000),以及m个长度为n的DNA序列(只包含字母A,C,G,T),输出到m个序列的Hamming距离和最小的DNA序列和对应的距离。如有多解,要求为字典序最小的解。例如,对于下面5个DNA序列,最优解为TAAGATAC。

TATGATAC

TAAGCTAC

AAAGATCC

TGAGATAC

TAAGATGT 

#include <stdio.h>

int main()
{
	int T;
	scanf("%d", &T);
	while(T--)
	{
		int m, n;
		scanf("%d%d", &m, &n);
		char DNA[m][n + 1];
		for(int i = 0; i < m; i++)
			scanf("%s", DNA[i]);
		int a[n] = {0}, c[n] = {0}, g[n] = {0}, t[n] = {0},	//用于计数 
			max[n] = {0}, distance[n] = {0}, distance_sum = 0;
		char solution[n + 1];
		for(int j = 0; j < n; j++)	//每一列字符计数,众数即为最优解所在列的字符 
		{
			for(int i = 0; i < m; i++)
				switch(DNA[i][j])
				{
					case 'A':
						a[j]++; break;
					case 'C':
						c[j]++; break;
					case 'G':
						g[j]++; break;
					case 'T':
						t[j]++; break;
				}
			
			max[j] = a[j], solution[j] = 'A', distance[j] = c[j] + g[j] + t[j]; 		 
			if(c[j] > max[j])	//比较中隐藏了按字典序比较,顺序比较即可 
				max[j] = c[j], solution[j] = 'C', distance[j] = a[j] + g[j] + t[j];
			if(g[j] > max[j]) 	//按列除最优解字符外相加即为距离
				max[j] = g[j], solution[j] = 'G', distance[j] = a[j] + c[j] + t[j];
			if(t[j] > max[j])
				max[j] = t[j], solution[j] = 'T', distance[j] = a[j] + c[j] + g[j];
		}
		solution[n] = '\0';	//添加字符串末尾结束标志 
		printf("到m个序列的Hamming距离和最小的DNA序列:%s\n", solution);
		for(int j = 0; j < n; j++)
			distance_sum += distance[j];	//距离和 
		printf("对应的距离:%d\n", distance_sum); 
	}
	return 0;
}

 注:一开始审题错了,产生歧义,以为从m个序列之中寻找最优解,其实是从m个序列之外寻找最优解。

附(从m个序列之中寻找最优解):

//审题产生歧义,以为从m个序列之中寻找最优解 
#include <stdio.h>
#include <string.h>

int main()
{
	int m, n;
	scanf("%d%d", &m, &n);
	char DNA[m][n + 1];
	for(int i = 0; i < m; i++)
		scanf("%s", DNA[i]);
	int a[m][m];
	memset(a, 0, sizeof(a));	//确保初始化为0 
	for(int i = 0; i < m; i++) 
		for(int j = 0; j < m; j++)
		{
			if(i == j)
				continue;
			for(int k = 0; k < n; k++)
				if(DNA[i][k] != DNA[j][k])
					a[i][j]++;
		}
	/*for(int i = 0; i < m; i++)
		for(int j = 0; j < m; j++)
			printf("a[%d][%d] = %d\n", i, j, a[i][j]);*/
	int sum[m] = {0}, min = 100, t;
	for(int i = 0; i < m; i++)
	{
		for(int j = 0; j < m; j++)
			sum[i] += a[i][j];
		if(sum[i] < min)
		{
			t = i;
			min = sum[i];
		}
		else if(sum[i] == min)
			if(strcmp(DNA[t], DNA[i]) > 0)
			{
				t = i;
				min = sum[i];
			}
			else
				continue;
	}
	printf("到m个序列的Hamming距离和最小的DNA序列:%s\n", DNA[t]);
	for(int i = 0; i < m; i++)
		printf("它对应到第%d个序列的距离:%d\n", i, a[t][i]);
	return 0;
} 

习题3-8 循环小数(Repeating Decimals,ACM/ICPC World Finals 1990,UVa202)

         输入整数a和b(0≤a≤3000,1≤b≤3000),输出a/b的循环小数表示以及循环解长度。例如a=5,b=43,小数表示为0.(116279069767441860465),循环节长度为21。

这道题主要就是小数部分的处理,想转化为字符串处理,发现还是有点复杂,还是直接参考其他作者的代码,这个是我认为写的比较清晰简洁的,请参考:

#include<stdio.h>

int res[3005], mod[3005];	//res数组记录每一次的商,mod数组记录每一次的余数 
int main()
{
 	int a, b;
 	while(scanf("%d%d", &a, &b) != EOF)
 	{
  		int i = 0, j;
  		int flag = 0;	//标记是否找到了循环节 
  		printf("%d/%d = %d.", a, b, a/b);	//先输出整数部分 
  		while(true)
		{
   			res[i]= a / b;
    		mod[i]= a % b;
      		a=(a % b) * 10; //将这次的余数乘以十作为下一次的被除数 
      		for(j = 1; j < i; j++)	//与前面出现的商和余数进行比较;注意是从j=1,即小数点后进行比较 
       			if(res[j] == res[i] && mod[j] == mod[i])
				{	//若余数和商的组合在前面出现过,则表示出现循环节 
        			flag = 1;	//此时的i为第二次出现循环节的开头第一个数字的位置 
        			break;
    			}
   			if(flag)
    			break;
   			i++;
  		}
  		for(int k = 1; k < j; k++)	//注意到res[0]中存储的是整数部分 
   			printf("%d", res[k]);	//输出小数点后非循环节部分 
  		printf("(");
  		if(i > 50)
		{	//如果要输出的小数部分位数大于50,就只输出前50个 
   			for(int k = j; k <= 50; k++)
    			printf("%d", res[k]);
   			printf("...)\n");
  		}
		else
		{
   			for(int k = j; k < i; k++)
    			printf("%d", res[k]);
   			printf(")\n");
  		}
 		printf("   %d = number of digits in repeating cycle\n\n", i-j); //注意输出格式,循环节长度前有三个空格,末尾两个换行符 
	}
 	return 0;
}

 注:原文链接:【算法竞赛入门经典】习题3-8循环小数(Uva202)_GUANYX的博客-CSDN博客

习题3-9 子序列(All in All,UVa10340) 

         输入两个字符串s和t,判断是否可以从t中删除0个或多个字符(其他字符顺序不变),得到字符串s。例如,abcde可以得到bce,但无法得到dc。

粗看题目,似乎略复杂,其实不然,我们用不着删除字符,只需顺序遍历两个序列即可。

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

int main() 
{ 
    char s[10000], t[10000];
	while(scanf("%s%s", s, t) != EOF)
	{
		int i, j = 0;
		for(i = 0 ; i < strlen(t); i++)
			if(t[i] == s[j])	//顺序寻找字符串t中是否含有字符串s中的字符即可 
				j++;
		if(j >= strlen(s))	//字符串s遍历完全即符合 
			printf("Yes\n");
		else
			printf("No\n");
	}
    return 0; 
} 

 刚开始,注意力集中在字符串t其内寻找的s字符串中的字符顺序一定要越来越后,便想到用strchr函数和strncpy函数结合,找到就截取后面的字符串再找。后来想到,这不就是顺序遍历两个字符串嘛,自加就可以了哈哈。

字符串处理函数版:

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

int main()
{
	char s[10000], t[10000];
	while(scanf("%s%s", s, t) != EOF)
	{
		char *ptr = 0, c, a[10000];
		strcpy(a, t);	//考虑到字符串t需要截取,拷贝字符串t给字符串a 
		int i;
		for(i = 0; i < strlen(s); i++)
		{
			c = s[i];
			ptr = strchr(a, c);	//顺序寻找字符串s中字符c在字符串a(t)第一次出现的位置 
			if (ptr) 
				strncpy(a, ptr + 1, strlen(a) - (ptr - a));	//截取字符串a中字符c后面的字符串 
    		else
			{
       			printf("No\n");
       			break;
			}
		}
		if(i >= strlen(s))
			printf("Yes\n");
	}
	return 0;
} 

注:C 库函数 char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。

习题3-10 盒子(Box,ACM/ICPC NEERC 2004,UVa1587)

        给定6个矩形的长和宽wi和hi(1≤wi,hi≤10000),判断它们能否构成长方体的6个面。

 思路:

第一种:正方体(aa=6),只出现一条边a
第二种:侧面相等的长方体(ab == 4 && aa == 2),只出现两条边a,b
第三种:一般长方体(ab == 2 && ac == 2 & bc == 2),只出现三条边a,b,c

注意:按构成长方体的矩形面划分只有这三种情况

(这种思路好像不太灵活,但是我觉得是比较容易理解的,暂时没有其他思路)

#include <stdio.h>

struct Rectangle		
{
	int h;
	int w;	
}rect[6];	//定义矩形结构体数组 

int main()
{
	int T;				 	
	scanf("%d", &T);	 
	while(T--)
	{
		for(int i = 0; i < 6; i++)
			scanf("%d%d", &rect[i].h, &rect[i].w);
		int aa = 0, bb = 0, ab = 0, ac = 0, bc = 0;
		int a = rect[0].h, b = rect[0].w, c, flag = 0;	//将第一组数据设为a和b,flag用于判断c是否出现过 
		for(int i = 0; i < 6; i++)
		{		//下面的判断语句就是边长a,b,c的组合问题,稍微比划两下就很明了 
			if(rect[i].h == a && rect[i].w == a)	
				aa++;
			else if(rect[i].h == b && rect[i].w == b)
				bb++;
			else if(rect[i].h == a && rect[i].w == b || 
						rect[i].h == b && rect[i].w == a)
				ab++;
			else if(rect[i].h == a && rect[i].w != b && !flag)
			{
				c = rect[i].w;
				flag = 1; 
				ac++;
			}
			else if(rect[i].h == b && rect[i].w != a && !flag)
			{
				c = rect[i].w;
				flag = 1;
				bc++;
			}
			else if(rect[i].w == a && rect[i].h != b && !flag)
			{
				c = rect[i].h;
				flag = 1;
				ac++;	
			}
			else if(rect[i].w == b && rect[i].h != a && !flag)
			{
				c = rect[i].h;
				flag = 1;
				bc++;	
			}
			else if(rect[i].h == a && rect[i].w == c || 
						rect[i].h == c && rect[i].w == a)
				ac++;
			else if(rect[i].h == b && rect[i].w == c || 
						rect[i].h == c && rect[i].w == b)
				bc++;
			else
				break;	//其他情况说明出现第四条边,直接退出		
		}
		//printf("aa=%d,bb=%d,ab=%d,ac=%d,bc=%d\n", aa, bb, ab, ac, bc);
        if(aa == 6 || (ab == 4 && aa == 2) || (ab == 4 && bb == 2) || (ab == 2 && ac == 2 & bc == 2)) 
        	printf("POSSIBLE\n");	 //符合以上条件的6组数据则可以构成长方体 
   		else
        	printf("IMPOSSIBLE\n"); 
	}
	return 0;	
}

习题3-11 换抵挡装置(Kickdown,ACM/ICPC NEERC 2006,UVa1588)

        给出两个长度分别为n1, n2(n1,n2≤100)且每列高度只为1或2的长条。需要将它们放入一个高度为3的容器(如图3-8所示),问能够容纳它们的最短容器长度。 

(书上没有写输入输出格式,网上找的)

Sample Input                    
2112112112

2212112

12121212

21212121

2211221122

21212

Sample Output

10

8

5

思路:一开始没有输入输出有点懵,想了一下可以将长条数字化 ,果然是这样的。思路就是固定一个长条,先得清楚哪个长条更长,哪个更短,两个长条完全接触的时候长的便是最短容器长度;移动一个长条,计算每一个接触的地方累加的高度是否符合题意。需要注意的是,长条需要完全在固定长条的左端开始移动,否则会漏掉符合的长条移动位置。

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

int main()
{ 
	char s1[105], s2[105], a[105], b[105];
	while(scanf("%s%s", s1, s2) != EOF)
	{
		int n1 , n2 ;
		if(strlen(s1) <= strlen(s2))	//固定长度最小的长条为a,长度为n1,拷贝给a 
			strcpy(a, s1), strcpy(b, s2), n1 = strlen(s1), n2 = strlen(s2);
		else
			strcpy(a, s2), strcpy(b, s1), n1 = strlen(s2), n2 = strlen(s1);
		int n, min = n1 + n2;	 //b从a左端开始移动,每次移动一个单位长度,n为移动次数,设置min初始值为两长条长度之和 
		for(n = 0; n < n1 + n2; n++)
		{
			int i = 0;
			if(n <= n1)	//b最右端未超出a 
			{
				for(i = 0; i < n; i++)
					if((a[i] - '0' + b[n2 - n + i] - '0') > 3)
						break;
				if(i >= n && n1 + n2 -n < min)
					min = n1 + n2 -n;	//最短容器长度等于长条a和b的总长减去接触部分(b的移动次数n) 
			}
			else if(n > n1 && n <= n2)	//b最左端未超出a 
			{
				for(i = 0; i < n1; i++)
					if((a[i] - '0' + b[n2 - n + i] - '0') > 3)
						break;
				if(i >= n1 && n2 < min)
					min = n2;	//最短容器长度等于较长的那根长条(b)长度 (n2) 
			}
			else	//b最左端超出a 
			{
				for(i = n - n2; i < n1; i++)
					if((a[i] - '0' + b[n2 - n + i] - '0') > 3)
						break;
				if(i >= n1 && n < min)
					min = n;	//最短容器长度等于b的移动次数 
			}
		}
		printf("%d\n", min);
	}
	return 0;
}

这个方法偏暴力,网上有位大佬利用对称性简化了繁琐过程,请参考:

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
 
bool test(int k,char s1[],char s2[]){
    for(int i = 0; s1[k+i] && s2[i]; i++)//这里的循环终止条件很值得学习!
        if(s1[k+i]+s2[i]-2*'0' > 3)return false;
    return true;
}
 
int fun(char s1[],char s2[]){
    int k = 0;
    while(!test(k,s1,s2))k++;//s1固定,向右移动s2
    return max(strlen(s1),strlen(s2)+k);
}
 
int main(){
    char bottom[105],top[105];
    while(scanf("%s%s",bottom,top) != EOF)//对称性
        cout<<min(fun(bottom,top),fun(top,bottom))<<endl;
    return 0;
}

注:原文链接:习题3-11 换低挡装置 UVa1588_Skyline-CSDN博客

习题3-12  浮点数(Floating-Point Numbers,UVa11809)  

        计算机常用阶码-尾数的方法保存浮点数。如图3-9所示,如果阶码有6位,尾数有8位, 可以

表达的最大浮点数为0.1111111112×211111120.1111111112×21111112。注意小数点后第一位必须为

1,所以一共有9 位小数。

图3-9 阶码-尾数保存浮点数

        这个数换算成十进制之后就是0.998046875*2^63=9.205357638345294*10^18。你的任务是

根据这个最大浮点数,求出阶码的位数E和尾数的位数M。输入格式为AeB,表示最大浮点数为

A*10^B。0<A<10,并且恰好包含15位有效数字。输入结束标志为0e0。对于每组数据,输出M和

E。输入保证有唯一解,且0≤M≤9,1≤E≤30。在本题中,M+E+2不必为8的整数倍。

 (这道题设计到了作者不太擅长的数据存储知识,题目都没怎么整懂,看了其他作者的分析,略微明白了其思路),在这里选取了一位大佬的题解,请参考:

上面的图就给出了一个例子,前面的0表示是正数。后面8位表示尾数m,这里是0.111111111(注意后面是9个1,因为头一个省略了)。之后那个0表示分割,最后面6位表示e的二进制为111111。所以这个数就是,用十进制表示就是。

  在计算机中用二进制表示M和E的时候如果位数不同,那么它们所能表示的最大值也不同。现在给你所能够表示的最大的浮点数的值,让你倒回去求M和E分别有多少位。输入格式为AeB,表示最大浮点数为,并且0 < A < 10,并且保证输出的结果中0 ≤ M ≤ 9且1 ≤ E ≤ 30。输入以0e0表示结束,0e0本身不计算。


  这个如果直接去算的话相当麻烦,当E很大的时候数会直接超出上限。这个时候可以反过来想,最大的时候M和E的每一位肯定都是1,并且又有0 ≤ M ≤ 9且1 ≤ E ≤ 30的限定,所以一共只有300种情况,自然就想到了打表,先用二重循环枚举M和E可能出现位数的所有情况打一张表,然后输入的时候倒回去找即可。


  假设当前一层M和E的值为m和e,它们的位数分别为i和j。

  首先计算m的值,用二进制表示的话,m的值为0.11…,也就是m = 2^(-1) + 2^(-2) + … + 2^(-1 - i)(i比实际1的个数少1个),也就是m = 1 - 2^(-1 - i)。

  接下来就是计算e的值,不难得出,e = 2^j - 1。

  那么也就有m * 2^e = A * 10^B,似乎可以直接计算了。然而,直接这样算的话是不行的,因为当e太大的话(e最大可以是1073741823,注意这还只是2的指数),等号左边的数就会超出上限,所以要想继续算下去,就得自己去想办法再写出满足要求的类来,这显然太麻烦了。所以,这个时候我们对等式两边同时取对数,这个时候就有 log10(m) + e × log10(2) = log10(A) + B。因为此时m和e的值都是确定的,所以不妨令等式左边为t,也就有t = log10(A) + B。

  这个时候就有问题了,A和B怎么算。

  写题解的时候突然意识到了这个问题,读题的时候很多人,包括我,都把AeB默认为了科学记数法,在ACM协会群里面讨论的时候很多人也都说这是科学计数法。先来看如果是科学记数法的时候应该怎么办。

  如果是科学记数法的话,那么对于A,就有1 ≤ A < 10。那么0 < log10(A) < 1。所以t的小数部分就是log10(A),整数部分就是B,即B = ⌊t⌋,A = 10^(t - B)。那么接下来,我们只需要开出两个二维数组来,分别记录对应i和j下A和B的大小,之后从输入里提取出A和B的大小,去二维数组里面查找对应的i和j即可。

  这种办法在UVA上面是可以直接AC的,但是我却感觉这题这样A了有点数据太水的感觉,秉着处女座+强迫症死磕到底的精神,我们看下哪里有问题。

  其实回头读下题,我们发现科学记数法1 ≤ A < 10的条件是我们脑补出来的,题目里面根本没有提及,只是简单交待0 < A < 10。也就是说,对于确定的M和E的位数,十进制的表示可以有多种,例如样例中的5.699141892149156e76,下面的数据应当也是完全可能的,而且结果应当与样例的结果是相同的(当然是在保证精度可以计算出结果的前提下):


0.569914189214915e77
0.056991418921491e78
0.005699141892149e79
0.000569914189214e80
  带着这个想法我分别拿着上面的数据去UVA toolkit和uDebug上试了试, UVA toolkit依旧能够输出“5 8”的结果来,但是uDebug告诉我我的输入不合法……果真是我想多了么……
  不过这个问题也好办,还是看上面的数据,忽略掉后面几位精度丢失的问题的话,上面的几个数完全可以通过“A *= 10, B -= 1”或者“A /= 10, B += 1”的操作来相互转化。那么对于0 < A < 1的A的值,我们就可以通过“A *= 10, B -= 1”的操作来使其满足科学记数法的条件。

  另外,在查表的时候还应该注意精度的问题,15位有效数字对于double来说精度似乎也不够,而且计算出所需要的整数值其实需要的精度也没有那么高,所以这里的精度就只用到了1e-4的程度。

————————————————
版权声明:本文为CSDN博主「crazysillynerd」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:UVA 11809 - Floating-Point Numbers______-CSDN博客

#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
 
using namespace std;
 
int main() {
    double M[20][40];
    long long E[20][40];
 
    // 打表
    for(int i = 0; i <= 9; ++i) for(int j = 1; j <= 30; ++j) {
        double m = 1 - pow(2, -1 - i), e = pow(2, j) - 1;
        double t = log10(m) + e * log10(2);
        E[i][j] = t, M[i][j] = pow(10, t - E[i][j]);
    }
 
    // 输入并输出结果
    string in;
    while(cin >> in && in != "0e0") {
        // 处理输入
        for(string::iterator i = in.begin(); i != in.end(); ++i) if(*i == 'e') *i = ' ';
        istringstream ss(in);
        double A; int B;
        ss >> A >> B;
        while(A < 1) A *= 10, B -= 1;
        // 在打好的表中寻找答案
        for(int i = 0; i <= 9; ++i) for(int j = 1; j <= 30; ++j) {
            if(B == E[i][j] && (fabs(A - M[i][j]) < 1e-4 || fabs(A / 10 - M[i][j]) < 1e-4)) {
                cout << i << ' ' << j << endl;
                break;
            }
        }
    }
}

根据原作者代码转化为C语言版(由于C++没有入门,根据自己的理解和编写习惯,通过浏览相关语句用法来转化为C语言表述):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

int main() 
{
    double M[20][40];
    long long E[20][40];
    // 打表
    for(int i = 0; i <= 9; ++i) 
		for(int j = 1; j <= 30; ++j)
		{
        	double m = 1 - pow(2, -1 - i), e = pow(2, j) - 1;
        	double t = log10(m) + e * log10(2);
        	E[i][j] = t, M[i][j] = pow(10, t - E[i][j]);
    	}
    // 输入并输出结果
    char in[25];
    while(scanf("%s", in) == 1) 
	{
		if(strcmp("0e0",in) == 0)
            break;
        // 处理输入
        for(char *i = in; i < in + strlen(in); ++i) 
			if(*i == 'e') 
				*i = ' ';
        char * p;
        p = strtok(in, " ");
        double A = atof(p);
        p = strtok(NULL,"\0");
        int B = atoi(p);
        while(A < 1) 
			A *= 10, B -= 1;
        // 在打好的表中寻找答案
        for(int i = 0; i <= 9; ++i) 
			for(int j = 1; j <= 30; ++j) 
			{
            	if(B == E[i][j] && (fabs(A - M[i][j]) < 1e-4 || fabs(A / 10 - M[i][j]) < 1e-4)) 
				{
                	printf("%d %d\n", i, j);
                	break;
            }
        }
    }
}

至此,算法竞赛入门经典(第二版)的所有习题完结,发现大部分题目都涉及到了字符串,拓展了相关方面的知识,但还发现自己还欠缺许多方面的知识,希望能够好好学完这本书,感谢!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值