《算法竞赛入门经典(第2版)》第三章习题题解

《算法竞赛入门经典》习题源码 Github开源:https://github.com/RyanHe123/Classic-Introduction-to-Algorithmic-Competition
本文中的习题题解为本人完成,如果存在错误或可以改进的地方,欢迎在评论区提出,谢谢!

习题3-1 得分(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>
int main()
{
	int n;
	scanf("%d", &n);
	int i = 0;
	getchar();
	for (; i < n; i++)
	{
		char ch; int sum = 0;
		int state = 0;
		while ((ch = getchar()) !='\n')
		{
			if (ch == 'O')
			{
				state++;
				sum += state;
			}
			else
			{
				state = 0;
			}
		}
		printf("%d\n", sum);
	}
}

习题3-2 分子量(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<ctype.h>
#include<string.h>
char s[90];
int main()
{
	int n;
	scanf("%d",&n);
	int i=0;
	for(;i<n;i++)
	{
		double atom;
		int count=1;
		scanf("%s",s);
		int j;
		double sum=0;
		for(j=0;j<strlen(s);j++)
		{
			switch(s[j])
			{
				case 'C':atom=12.01;break;
				case 'H':atom=1.008;break;
				case 'O':atom=16.00;break;
				case 'N':atom=14.01;break;
			}
			if(isdigit(s[j+1])!=0)
				count=s[++j];
			while(isdigit(s[j+1])!=0)
				count=count*10+s[++j];
			sum+=count*atom;
		}
		printf("%.3f\n",sum);
	}
}

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

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

#include<stdio.h>
#include<math.h> 
int main()
{
	int t;
	scanf("%d", &t);
	int i;
	for (i = 0; i < t; i++)
	{
		int a[10] = { 0 };
		int n;
		scanf("%d", &n);
		int j;
		for (j = 1; j <= n; j++)
		{
			int num = j;
			while (num != 0)
			{
				a[num % 10]++;
				num /= 10;
			}
		}
		for (j = 0; j < 9; j++)
			printf("%d ", a[j]);
		printf("%d\n", a[9]);
	}
}	

习题3-4 周期串(UVa 455)

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

#include<stdio.h>
#include<string.h>
int main()
{
	int n;
	scanf("%d",&n);
	int i;
	for(i=0;i<n;i++)
	{
		char s[100];
		scanf("%s",s);
		int t;
		for(t=1;t<=strlen(s);t++)
		{
			//一开始忽略了结尾不是完整周期结束的情况 
			if(strlen(s)%t!=0)
				continue;
			int j=0,state=1;
			for(;j<strlen(s);j++)
			{
				if(s[j]!=s[j%t])
				{
					state=0;
					break;
				}
			}
			if(state==1)
				{
					if(i!=0)printf("\n");
				printf("%d\n",t);break;
				}
		}
	}
}

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

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

//这道题的输出格式有很多坑,PE了无数次 
#include<stdio.h>
int main()
{
	 //输入矩阵
	int kase=1,state;
	int i,j;
	for(;;)
	{
	state=1;
	int x,y;
	char p[5][6],ch;
	for(i=0;i<5;i++)
	{
		fgets(p[i],7,stdin);
		if(p[0][0]=='Z'&&p[0][1]=='\n')
			return 0;
 	}
 	//搜索空格位置 
	for(i=0;i<5;i++)
	{
		for(j=0;j<5;j++)
		{
			if(p[i][j]==' ')
			{x=i;y=j;}
		}
	} 	
 	while((ch=getchar())!='0')
 	{
 		switch(ch)
 		{
 			case'A':if(x==0)state=0;
			 else{p[x][y]=p[x-1][y];p[x-1][y]=' ';x--;}break;	//边界判断和移动
 			case'B':if(x==4)state=0;
			 else{p[x][y]=p[x+1][y];p[x+1][y]=' ';x++;}break;
 			case'L':if(y==0)state=0;
			 else{p[x][y]=p[x][y-1];p[x][y-1]=' ';y--;}break;
 			case'R':if(y==4)state=0;
			 else{p[x][y]=p[x][y+1];p[x][y+1]=' ';y++;}break;
		}
	}
	//输出 
	if(kase!=1)
	printf("\n");
	printf("Puzzle #%d:\n",kase++);
	if(state==0)
	printf("This puzzle has no final configuration.\n");
	else
	{
	for(i=0;i<5;i++)
	{
		for(j=0;j<5;j++)
		{
			printf("%c",p[i][j]);
			if(j!=4)printf(" "); 
		}
		printf("\n");
	} 	
	}
	 getchar();
	}
}

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

输入一个r行c列的网格,,黑格用‘*’表示,每个白格都填有一个字母。如果一个白格的左边相邻位置或者上边相邻位置没有白格(可能是黑格,也可能出了网格边界),则称这个白格是一个起始格。首先把所有的起始格从上到下,从左到右的顺序编号为1,2,3…

#include<stdio.h>
int main()
{
	int a,b;
	int kase=1;
	while(scanf("%d",&a)&&a!=0)
	{
		scanf("%d",&b);
		if(kase!=1)
		printf("\n"); 
		char p[a][b+1];int n[a][b];
		int i,j;
		getchar();//用这行吞掉输入a和b时产生的\n 
		for(i=0;i<a;i++)
			for(j=0;j<b;j++)n[i][j]=0;//给n数组初始化0 
		//读取行列式
		for(i=0;i<a;i++)
			fgets(p[i],b+2,stdin);
		//标记起始格
		int count=1;
		for(i=0;i<b;i++)
		if(p[0][i]!='*')n[0][i]=count++;		//标记第一行 
		for(i=1;i<a;i++)
		{
			if(p[i][0]!='*')n[i][0]=count++;
			for(j=1;j<b;j++)
			if((p[i][j-1]=='*'||p[i-1][j]=='*')&&p[i][j]!='*') n[i][j]=count++;
		}
		/*for(i=0;i<a;i++)
		for(j=0;j<b;j++)
		{
			printf("%d ",n[i][j]);d
			if(j==b-1)printf("\n");
		}*/
		//寻找符合条件单词 
		printf("puzzle #%d:\nAcross\n",kase++);
		//横向 
		for(i=0;i<a;i++)
		for(j=0;j<b;j++)
		{
			if(j==0&&p[i][0]!='*')
			{
				printf("%3d.",n[i][0]);
				while(p[i][j]!='*'&&j<b)
				putchar(p[i][j++]);
				printf("\n");
			}
			while(p[i][j]=='*'&&j<b){
				j++;
			}
			if(j>=b)break;
			printf("%3d.",n[i][j]);
			while(p[i][j]!='*'&&j<b)
				putchar(p[i][j++]);
				printf("\n");
		 } 
		//纵向 还是要按照横向顺序搜索,比横向复杂 
		printf("Down\n");
		int temp; 
		for(i=0;i<a;i++)
		for(j=0;j<b;j++)
		{
			if(i==0&&p[0][j]!='*')
			{
				printf("%3d.",n[0][j]);
				temp=i;
				while(p[temp][j]!='*'&&temp<a)
				putchar(p[temp++][j]);
				printf("\n");
			}
			if(i>0&&p[i][j]!='*'&&p[i-1][j]=='*')
			{
				printf("%3d.",n[i][j]);
				temp=i;
				while(p[temp][j]!='*'&&temp<a)
				putchar(p[temp++][j]);
				printf("\n");
			}

		 } 
		 
	} 
	
 } 

习题3-7 DNA序列(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 n;
	scanf("%d", &n);
	int t;
	for (t = 0; t < n; t++)
	{
		int m, n;
		scanf("%d %d", &m, &n);
		char s[50][1100];
		int i,j;
		for (j = 0; j < m; j++)
			scanf("%s", s[j]);
		//读取字符串,二维数组 
		
		int sum = 0;
		for (j = 0; j < n; j++)
		{
			int a[4] = { 0 };
			for (i = 0; i < m; i++)
			{
				
				//逐位读取出现次数 
				switch (s[i][j])
				{
				case'A':a[0]++; break;
				case'C':a[1]++; break;
				case'G':a[2]++; break;
				case'T':a[3]++; break;
				}
			}
			int max = 0, index = 0;
			int z;
			//找到出现次数最多的字母 
			for (z = 0; z < 4; z++){
				if(a[z]==max)
				{
					if(z<index)
					index=z;
				}
				if (a[z] > max)
				{
					index=z;
					max = a[z];  // 如果出现次数相同输出字典序小的解 
				}
			}
			sum += m - max;	
			switch (index)
			{
			case 0:putchar('A'); break;
			case 1:putchar('C'); break;
			case 2:putchar('G'); break;
			case 3:putchar('T'); break;
			}
		}
		printf("\n%d\n", sum);
	}

}

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

输入整数a和b a大于等于0小于等于3000,b大于等于1小于等于3000,输出a/b的循环小数表示以及循环节长度。如果循环周期大于50,只显示50位,之后的全部用…表示

#include<stdio.h>
#include<string.h>
struct combine
{
	int x, y;
};// 记录除数和余数 
combine c[2000] = { 0 }; int yu[3000] = { 0 };//记录余数 ,数组一定要开在main外面,不然会出问题 
int main()
{
	int a, b;
	while (EOF != scanf("%d", &a))
	{
		scanf("%d", &b);
		int n = a;
		int d = a / b;
		c[0].y = a % b;
		yu[a%b]++;
		a = a % b * 10;
		int i;
		for (i = 1;; i++)//当有相同余数时,停止循环 
		{
			c[i].x = a / b; c[i].y = a % b;
			if (yu[a%b] == 1)
				break;
			else
				yu[a%b]++;
			a = a % b * 10;
		}
		int j, l;
		for (j = 0;; j++)//寻找前一次出现相同余数的位置 
		{
			if (c[j].y == a % b)
			{
				l = i - j;
				break;
			}
		}
			printf("%d/%d = %d.", n, b, d);
			int t;
			for (t = 1; t <= j; t++)
				printf("%d", c[t].x);
			printf("(");
			for (; t <= i; t++)
			{
				if (t == 51)
				{
					printf("..."); break;
				}
				printf("%d", c[t].x);
			}
			printf(")\n   %d = number of digits in repeating cycle\n\n",l);//此题的输出格式给的很模糊,需要在每个output之间加一个blank line 
		memset(c, 0, sizeof(c));
		memset(yu, 0, sizeof(yu));
	}
}

习题3-9 子序列(UVa10340)

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

#include<stdio.h>
#include<string.h>
//字符串一定要开大一些,刚开始开的比较小,就RE了 
char s[1000000],t[1000000];
int main()
{
	//读取字符串
	while((s[0]=getchar())!=EOF)
	{
		int i=1;
		while((s[i]=getchar())!=' ')
			i++;
		s[i]='\0';
		i=0;
		while((t[i]=getchar())!='\n')
			i++;
		int index=0; 
		for(i=0;i<strlen(t);i++)
	{
		if(s[index]==t[i])
			index++;
		
	}
		if(index==strlen(s))
		printf("Yes\n");
		else
		printf("No\n");
		//初始化数组,防止上一次实验对下一次有影响 
		memset(s,0,sizeof(s));
		memset(t,0,sizeof(t));
	 } 
}

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

多实例测试。给出6个矩形的长和宽,判断这6个面是不是可以围成一个长方体,若可以围成,则输出“POSSIBLE”(不包含引号),否则输出“IMPOSSIBLE”(不包含引号),每个输出占一行。

struct rectangle
{
	int x, y;
};
void internal_swap(struct rectangle &r1)
{
	if (r1.x > r1.y)
	{
		int temp;
		temp = r1.x;
		r1.x = r1.y;
		r1.y = temp;
	}
}
void external_swap(rectangle &r1, rectangle &r2)
{
	if (r1.x > r2.x)
	{
		rectangle temp;
		temp = r1;
		r1 = r2;
		r2 = temp;
	}
	if (r1.x == r2.x&&r1.y > r2.y)
	{
		rectangle temp;
		temp = r1;
		r1 = r2;
		r2 = temp;
	}
}
#include<stdio.h>
int main()
{
	rectangle rec[6];
	while (scanf("%d %d", &rec[0].x,&rec[0].y)!=EOF)
	{
		internal_swap(rec[0]);
		int i;
		for (i = 1; i < 6; i++)
		{
			scanf("%d %d", &rec[i].x,&rec[i].y);
			internal_swap(rec[i]);
		}
		int j;
		for (i = 0; i < 5; i++)
		{
			for (j = 0; j < 5 - i; j++)
			{
				external_swap(rec[j], rec[j + 1]);
			}
		}
		int state = 1;
		for (i = 0; i < 6; i += 2)
		{
			if (rec[i].x != rec[i + 1].x || rec[i].y != rec[i + 1].y)
				state = 0;
		}
		if (rec[0].x != rec[2].x || rec[0].y != rec[4].x || rec[2].y != rec[4].y)
			state = 0;
		if (state == 1)
			printf("POSSIBLE\n");
		else
			printf("IMPOSSIBLE\n");
	}
}

习题3-11 换低档装置(ACM/ICPC NEERC 2006,UVa1588)

//a在前和b在前都尝试进行插入,对两个方向的最小长度进行比较,输出最小的长度
#include<stdio.h>
#include<string.h> 
int main()
{
	char a[200], b[200];
	while (EOF != scanf("%s", a))
	{
		scanf("%s", b);
		int la = strlen(a), lb = strlen(b), s1, s2, s;
		int i, j; int state_2 = 0;
		//a在前 
		for (i = 0; i < la; i++)
		{
			int state = 1;
			for (j = 0; j < lb; j++)
			{
				if (a[i + j] + b[j] - '0' - '0' > 3)
				{
					state = 0;
					break;
				}
			}
			if (state == 1) {
				//输出上和下中最长的那一个
				state_2 = 1;
				s1 =( (i + lb)>la? (i + lb):la); break;
			}
			if (state_2 == 0)
				s1 = la + lb;
		}
		state_2 = 0;
		//b在前 
		for (i = 0; i < lb); i++)
		{
			int state = 1;
			for (j = 0; j < la; j++)
			{
				if (b[i + j] + a[j] - '0' - '0' > 3)
				{
					state = 0;
					break;
				}
			}
			if (state == 1)
			{
				state_2 = 1;
				s2 = ((i + la) > lb ? (i + la) : lb);
				break;
			}
			if (state_2 == 0)
			{
				s2 = la + lb;
			}
		}
		if (s1 > s2)s = s2;
		else s = s1;
		printf("%d\n", s);
		memset(a, 0, sizeof(a));
		memset(b, 0, sizeof(b));
	}
	return 0;
}

习题3-12 浮点数(UVa11809)

这道题笔者想了很久都没想出来(是我太菜了),于是在网上找了大神的题解,贴在这里。链接:https://blog.csdn.net/crazysillynerd/article/details/43339157

//非原创,链接:https://blog.csdn.net/crazysillynerd/article/details/43339157 
#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;
            }
        }
    }
}
上一篇:背包九讲


下一篇:出学数据库写了个函数