算法笔记:数组和字符串

算法笔记:数组和字符串

内容来源:刘汝佳《算法竞赛入门经典(第2版)》

一、数组与字符数组

1.     一维/二维数组声明与使用方法(基础)

※注意两个函数:memcpy和memset

(1)memcpy(b, a,sizeof(type)*k):将数组a的前k个元素复制到数组b(type为数组类型,a数组与b数组的类型需相同)

(2)memset(a,value, sizeof(a)):将数组a的值均初始化为value

需要注意:当a数组为整型时,value只有为0、-1、一个很大的值(如0x7f)一个很小的值(如0xaf)时才可成功初始化;

当a数组为浮点型时,value=127,可将数组初始化为一个很大的值;value为0时可将数组清零。

例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int a[10]={1,2,3,4,5,6,7,8,9,10},b[10];
double d[5]={1.1,2.2,3.3,4.4,5.5};
int main()
{
	int i;
	int k=6;
	for(i=0;i<10;i++)
		printf("%d ",a[i]);
	printf("\n");
	
	memcpy(b,a,k*sizeof(int));
	for(i=0;i<k;i++)
		printf("%d ",b[i]);
	printf("\n");
	
	memset(b,0,sizeof(b));
	for(i=0;i<k;i++)
		printf("%d ",b[i]);
	printf("\n");
	
	for(i=0;i<5;i++)
		printf("%lf ",d[i]);
	printf("\n");
	
	memset(d,127,sizeof(d));
	for(i=0;i<5;i++)
		printf("%lf\n",d[i]);
	return 0;
}

2. 字符串的声明、赋值、比较和连接方法(基础)

(1)常见转义字符:\n(回车) \0(空字符)\\(反斜线) \’(单引号) \’’(双引号)

(2)字符串的读入举例:

scanf(“%s”,str);(读入字符串str,不含空白字符)

scanf(“%s”,str[i]);(读入第i个字符串,不含空白字符)

gets(str);gets(str[i]);(读入字符串str/第i个字符串,可包含空白字符)

char c; while(c=getchar()!=EOF){…}(可用于循环读入字符,含空白字符)

(3)字符串的输出举例:printf(“%s”,str); puts(str);


二、典例

1.    蛇形填数

在n*n方阵里填入1,2,...,n*n,要求填成蛇形。例如n=4时方阵为:

10 11 12 1

9 16 13 2

8 15 14 3

7 6 5 4

输入方阵维数(1<=n<=100),输出对应的n阶蛇形方阵。这里规定1填在方阵的右上角。

【分析】矩阵填数问题。

#include <stdio.h>
#include <string.h>
#define maxn 105
int n;
int mat[maxn][maxn];
void Fill(int mat[][maxn])
{
	int i=0,j=n-1;
	int num=1;
	mat[i][j]=num++;  //确定起点,填1 
	while(num<=n*n)
	{
		while(i+1<n && mat[i+1][j]==0)   //向下 
			mat[++i][j]=num++;
		while(j-1>=0 && mat[i][j-1]==0)  //向左 
			mat[i][--j]=num++;
		while(i-1>=0 && mat[i-1][j]==0)  //向上 
			mat[--i][j]=num++;
		while(j+1<n && mat[i][j+1]==0)   //向右 
			mat[i][++j]=num++;
	}
}
void Output(int mat[][maxn])
{
	int i,j;
	for(i=0;i<n;i++)
	{
		for(j=0;j<n-1;j++)
			printf("%d ",mat[i][j]);
		printf("%d\n",mat[i][n-1]);
	}
}
int main()
{
	scanf("%d",&n);
	memset(mat,0,sizeof(mat));
	Fill(mat);
	Output(mat);
	return 0;
}

2. 竖式问题

找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的集合。输入数字集合(相邻数字之间没有空格),输出所有竖式。每个竖式前对应有编号,之后应有一个空行。最后输出解的总数。

样例输入:2357

样例输出:

<1>
  775
X  33
-----
2325
2325
-----
25575
The number of solutions = 1

【分析】暴力求解、枚举、数位分离。

#include <stdio.h>
#include <string.h>
char numset[20]; 
int Judge(int num)
{
	int temp=num;
	int flag;
	int i,digit;
	while(temp!=0)    //分离num各位(用digit记录),并在numset中查找 
	{
		digit=temp%10;
		flag=0;
		for(i=0;i<strlen(numset);i++)  //查找 
		{
			if(digit==numset[i]-'0')   //此步表示找到digit 
			{
				flag=1;
				break;
			}
		}
		if(flag==0)   //digit在numset中找不到,直接跳出循环 
			break;
		temp/=10;
	}
	return flag;
}
void Print(int i,int j,int n1,int n2,int ret,int ans)
{
	printf("<%d>\n",ans);
	printf("%5d\n",i);
	printf("X%4d\n",j);
	printf("-----\n");
	printf("%5d\n",n1);
	printf("%4d\n",n2);
	printf("-----\n");
	printf("%5d\n\n",ret);
}
int main()
{
	int i,j;     //i-三位数 j-两位数
	int n1,n2,ret;
	int ans=0;
	scanf("%s",numset);
	for(i=100;i<1000;i++)     //枚举所有的i.j组合 
	{
		for(j=10;j<100;j++)
		{
			n1=i*(j%10);
			n2=i*(j/10);
			ret=i*j;
			if(Judge(i) && Judge(j) && Judge(n1) && Judge(n2) && Judge(ret))
			{
				ans++;
				Print(i,j,n1,n2,ret,ans);
			}
		}
	} 
	printf("The number of solutions = %d\n",ans);
	return 0;
}

3. Tex中的引号

在Tex中,左双引号是“``”,右双引号是“’’”。输入一篇包含双引号的文章,把它转换成Tex格式。

样例输入:

“To be or not to be,” quoth the Bard, “that

is the question”.

样例输出:

``To be or not to be,’’ quoth the Bard,``that

is the question’’.

【分析】多组输入输出。注意输入含有空格,考虑使用"while(c=getchar()!=EOF)"的形式。

#include <stdio.h>
int main()
{
	int c,q=1;
	while((c=getchar())!=EOF)   //循环读取,边读边处理且不需要完整保存输入,直到文件尾
	{
		if(c=='"')   //遇到双引号,根据出现次序处理 
		{
			printf("%s",q?"``":"''");   //左-右-左-右... 
			q=!q;
		}
		else         //否则原样输出输入的字符
			printf("%c",c); 
	} 
	return 0;
}

4. WERTYU

把手放在键盘上时,稍不注意就会往右错一位。这样输入Q就会变成W,输入J就会变成K等。

输入一个错位后敲出的字符串(所有字符均为大写字母),输出打字员本来想打出的句子。输入保证合法,即一定是错位后的字符串。例如输入中不会出现大写字母A。

样例输入:

O S, GOMR YPFSU/

样例输出:

I AM FINE TODAY.

【分析】多组输入。这里每输入一个字符,都可以直接输出一个字符,因此可用"while(c=getchar()!=EOF)"输入。在输入输出变换时可使用常量数组,以简化字符变换过程

即charchset[]="`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";

    ※常量数组的好处:不需指定大小,可由程序自动计算。

#include <stdio.h>
#include <string.h>
char ch;
char chset[]="`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";   //注意'\'处理为转义字符 
int main()
{
	int i,flag;
	while((ch=getchar())!=EOF)
	{
		flag=0;
		for(i=0;i<strlen(chset);i++)   //在常量数组中查找 
		{
			if(ch==chset[i])   //若找到,则变换 
			{
				flag=1;
				printf("%c",chset[i-1]);
			}
		}
		if(flag==0)    //否则,原样输出 
			printf("%c",ch);
	}
	printf("\n");
	return 0;
}

5. 回文词

输入一个字符串(长度不超过1000),判断它是否为回文串及镜像串。

回文串:反转后的串和原串相同,如abba和madam。

镜像串:左右镜像后的串和原串相同,如2S和3AIAE。

注意并不是每个字符在镜像之后都能得到一个合法字符。在本题中,每个字符的镜像如下图(空白项表示该字符镜像后不能得到一个合法字符):

样例输入:

NOTAPALINDROME

ISAPALINILAPASI

2A3MEAS

ATOYOTA

样例输出:

NOTAPALINDROME – is not a palindrome.

ISAPALINILAPASI – is a regular palindrome.

2A3MEAS – is a mirrored string.

ATOYOTA – is a mirrored palindrome.

【分析】注意这里的镜像串是一种特殊的回文串,且结果只有4中组合:

(1)非回文串

(2)仅回文串

(3)仅镜像串

(4)回文镜像串(最特殊的情况)

因此有以下判断步骤:

(1)首先判断输入的串是否是回文串,如果是,继续判断是否是镜像串。如果是,则该串既是回文串又是镜像串;否则该串仅仅是回文串;

(2)如果不是(1)的情况,则继续判断是否是镜像串,如果是,则该串仅仅是镜像串;否则该串不是回文串(当然也不是镜像串)。

#include <stdio.h>
#include <string.h>
#define maxn 1010
char str[maxn];
char ch[]="ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789";  //原字符集 
char re[]="A...3..HIL.JM.O...2TUVWXY51SE.Z..8.";  //镜像后字符集 
int is_Palindrome(char *str)      //回文串判断 
{
	int i=0,j=strlen(str)-1;
	while(i<j)
	{
		if(str[i]!=str[j])
			return 0;
		i++,j--;
	}
	return 1;
}
void Reverse(char *str)           //反转串 
{
	int i=0,j=strlen(str)-1;
	char t;
	while(i<j)
	{
		t=str[i];
		str[i]=str[j];
		str[j]=t;
		i++,j--;
	}
}
int is_Mirroredstr(char *str)     //镜像串判断 
{
	int i,j,k=0;
	char temp[maxn];
	for(i=0;i<strlen(str);i++)
	{
		for(j=0;j<strlen(ch);j++)
		{
			if(str[i]==ch[j])
			{
				temp[k++]=re[j];
				break;
			}
		}
	}
	temp[k]='\0';
	Reverse(temp);
	if(strcmp(temp,str)==0)
		return 1;
	else
		return 0;
}
int main()
{
	int i;
	while(scanf("%s",str)!=EOF)
	{
		if(is_Palindrome(str))
		{
			if(is_Mirroredstr(str))    //回文+镜像 
				printf("%s -- is a mirrored palindrome.\n",str);
			else    //仅回文 
				printf("%s -- is a regular palindrome.\n",str);
		}
		else
		{
			if(is_Mirroredstr(str))    //仅镜像 
				printf("%s -- is a mirrored string.\n",str);
			else     //非回文 
				printf("%s -- is not a palindrome.\n",str);
		}
	}
	return 0;
}

6. 猜数字游戏的提示

实现一个经典“猜数字”游戏。给出答案序列和用户猜测的序列,统计有多少数字位置正确(A),有多少数字在两个序列都出现过但位置不对(B)。

输入包含多组数据。 每组输入第一行为序列长度n,第二行是答案序列,接下来是若干猜测序列。 猜测序列全0时该组数据结束。 n=0时输入结束。

数据保证:1<=n<=1000,序列中的数均在[1, 9]内。

样例输入:

4

1 3 5 5

1 1 2 3

4 3 3 5

6 5 5 1

6 1 3 5

1 3 5 5

0 0 0 0

10

1 2 2 2 4 5 6 6 6 9

1 2 3 4 5 6 7 8 9 1

1 1 2 2 3 3 4 4 5 5

1 2 1 3 1 5 1 6 1 9

1 2 2 5 5 5 6 6 6 7

0 0 0 0 0 0 0 0 0 0

0

样例输出:

Game 1:

(1,1)

(2,0)

(1,2)

(1,2)

(4,0)

Game 2:

(2,4)

(3,2)

(5,0)

(7,0)

【分析】字符出现次数统计问题。

        直接统计可得A,为了求B,对于每个数字(1~9),统计二者出现的次数c1和c2,则min(c1, c2)就是该数字对B的贡献,最后减去A的部分。

#include <stdio.h>
#include <string.h>
#define maxn 1010
int n;
int answer[maxn];  //答案序列 
int guess[maxn];   //猜测序列 
int main()
{
	int i,num;
	int A,B,c1,c2; 
	int ncase=1;
	while(scanf("%d",&n)==1 && n)   //n=0输入结束 
	{
		printf("Game %d:\n",ncase++);
		for(i=0;i<n;i++)
			scanf("%d",&answer[i]);
		for(;;)
		{
			A=0,B=0;
			for(i=0;i<n;i++)
			{
				scanf("%d",&guess[i]);
				if(answer[i]==guess[i])
					A++;
			}
			if(guess[0]==0)  //正常猜测序列不会有0,所以只判断第一个数是否为0即可
				break;
			for(num=1;num<=9;num++)
			{
				c1=0,c2=0;
				for(i=0;i<n;i++)
				{
					if(answer[i]==num)
						c1++;
					if(guess[i]==num)
						c2++;
				}
				if(c1<c2)
					B+=c1;
				else
					B+=c2;
			} 
			printf("    (%d,%d)\n",A,B-A);
		}
	}
	return 0;
}
7. 生成元

如果x加上x的各位数字之和得到y,就说x是y的生成元。给出T(1<=T<=100)组测试数据及N(1<=N<=100000),求N的最小生成元。若无解,则输出0。

样例输入:

3

216

121

2005

样例输出:

198

0

1979

【分析】不难发现,假设所求生成元为M,则M<N。因此枚举所有的M<N即可,对每个M数位分离求和。

法一:

#include <stdio.h>
int N,T;
int main()
{
	int i;
	int sum,temp;
	int minelem;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);
		minelem=0;
		for(i=1;i<N;i++)
		{
			temp=i;
			sum=i;
			while(temp!=0)
			{
				sum+=(temp%10);
				temp/=10;
			}
			if(sum==N)
			{
				minelem=i;
				break;
			}
		}
		printf("%d\n",minelem); 
	}
	return 0;
}

但效率不够高,因为每次计算一个生成元都需要枚举N-1个数。因此可考虑“打表”:即一次性枚举所有100000以内的正整数M<N,标记“M加上M的各位数字之和得到的数有一个生成元是M”,最后查表即可。

法二:(“打表”)

#include <stdio.h>
#include <string.h>
#define maxn 100005
int N,T;
int ans[maxn];
int main()
{
	int i;
	int temp,sum;
	memset(ans,0,sizeof(ans));
	for(i=1;i<maxn;i++)
	{
		temp=i,sum=i;
		while(temp!=0)  //数位分离求和 
		{
			sum+=(temp%10);
			temp/=10;
		}
		if(ans[sum]==0 || i<ans[sum])  //填表
			ans[sum]=i; //下标sum存储数字,值i存储生成元 
	}
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&N);	
		printf("%d\n",ans[N]); 
	}
	return 0;
}
8. 环状序列

长度为n的环状串表示法,分别为从某个位置开始顺时针得到。例如图:


有10种表示:CGAGTCAGCT,GAGTCAGCTC,AGTCAGCTCG等。在这些表示法中,字典序最小的成为“最小表示”。

输入一个长度为n(1<=n<=100)的环状DNA串(只包含A,C,G,T)的一种表示法,你的任务是输出该环状串的最小表示。例如,CTCC的最小表示是CCCT;CGAGTCAGCT的最小表示为AGCTCGAGTC。

【分析】“字典序”问题

本题出现了一个新概念,字典序。字典序即字符串在字典中的顺序。

一般对于两个字符串,从第一个字符开始比较。当某一个的字符不同时,该位置字符较小的串,字典序较小(例如,abc比bcd小);如果其中一个字符串已经没有更多字符,但另一个字符串还没结束,则较短的字符串的字典序较小(例如,hi比history小)。

此外,字典序的概念可以推广到任意序列,例如,序列1,2,4,7,比1,2,5小。

学会了字典序的概念之后,本题就不难解决了:就像“求n个元素中的最小值”一样,用变量ans表示目前为止,字典序最小串在输入串中的起始位置,然后不断更新ans。

法一:对串进行“循环移位”,随时更新“最小表示”

#include <stdio.h>
#include <string.h>
#define maxn 105
int T;
char str[maxn];
char temp[maxn],ret[maxn];
int main()
{
	int i,t;
	char tch;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%s",str);
		strcpy(temp,str);
		strcpy(ret,str);
		t=0;
		while(t++<strlen(str))
		{
			tch=temp[0];
			for(i=1;i<strlen(temp);i++)
				temp[i-1]=temp[i];
			temp[strlen(temp)-1]=tch;
			if(strcmp(temp,ret)<0)
				strcpy(ret,temp);
		}
		printf("%s\n",ret);
	}
	return 0;
}

法二:结合对字典序问题的分析完成求解

#include <stdio.h>
#include <string.h>
#define maxn 105
int T;
char str[maxn];
int less(char *str,int p,int q)  //判断环状串s的表示法p是否比表示法q的字典序小
{
	int i;
	int len=strlen(str);
	for(i=0;i<len;i++)
	{
		if(str[(p+i)%len]!=str[(q+i)%len])
			return (str[(p+i)%len]<str[(q+i)%len]);
	}
	return 0;    //相等 
} 
int main()
{
	int i;
	int ans,len;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%s",str);
		ans=0;
		len=strlen(str);
		for(i=1;i<len;i++)  //循环更新字典序最小串在输入串中的起始位置 
		{
			if(less(str,i,ans)) 
				ans=i;
		}
		for(i=0;i<len;i++)  //以起始位置ans为起点,逐字符输出 
			printf("%c",str[(i+ans)%len]);
		printf("\n"); 
	}
	return 0;
}

三、练习

1.     得分

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

样例输入:

OOXXOXXOOO

样例输出:

10(1+2+0+0+1+0+0+1+2+3)

#include <stdio.h>
#include <string.h>
#define maxn 85
char str[maxn]; 
int main()
{
	int i;
	int sum,score;
	while(scanf("%s",str)!=EOF)
	{
		sum=0;
		score=1;
		for(i=0;i<strlen(str);i++)
		{
			if(str[i]=='O')
			{
				sum+=score;
				score++;
			}
			else
				score=1;
		}
		printf("%d\n",sum);
	}
	return 0;
}
2. 分子量

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

样例输入:

C6H5OH

C12H22O12

样例输出:

94.108

342.296

#include <stdio.h>
#include <math.h>
#include <string.h>
#define maxn 105
char str[maxn];
char elem[]="CHON";
double val[]={12.01,1.008,16.00,14.01};
int main()
{
	int i,j,k;
	int num;            //num记录原子个数 
	int sind,eind,flag; //sind.eind-数字字符起点与终点 flag-str[i]在每轮循环中能否找到 
	double sum;         //sum-分子量 
	while(scanf("%s",str)!=EOF)
	{
		i=0;
		sum=0;
		while(i<strlen(str))
		{
			flag=0;      //flag记录str[i]能否在elem常量数组中找到 
			for(j=0;j<strlen(elem);j++)
			{
				if(str[i]==elem[j])     //当前字符表示元素 
				{
					flag=1;
					if(!(str[i+1]>='0' && str[i+1]<='9') || str[i+1]=='\0')  //下一个字符不是数字或者当前字符为空,表示str[i]数目为1,然后累加 i++ 
					{
						sum+=val[j];
						i++;
					}
					else      //下一个字符是数字,要累加求和 
					{
						num=0;
						sind=i+1;   //数字字符起点:当前字符的后一个字符 
						i++;
						while(str[i]>='0' && str[i]<='9') //从数字字符起点开始,i++,直到第一个非数字字符出现或到达串尾 
							i++;
						eind=i;     //数字字符终点 
						for(k=sind;k<eind;k++)  //对区间[sind, eind)内的数字字符求和 
							num+=((str[k]-'0')*pow(10,eind-k-1));
						sum+=(val[j]*num);      //累加求和sum 
					}
					break; //找到立即退出循环 
				}
			}
			if(flag==0)    //找不到str[i],i++ 
				i++;
		}
		printf("%.3lf\n",sum);
	}
	return 0;
}
3.  数数字

把前n(1<=n<=10000)个正整数顺次写在一起:123456789101112…… 数一数0~9各出现多少次。

样例输入:

13(12345678910111213)

样例输出:

0:1

1:6

2:2

3:2

4:1

5:1

6:1

7:1

8:1

9:1

【分析】结合"数位分离"的思想,对[1, n]上的每个数分离数位,用一个数组保存每位(0~9)的出现次数。最后输出数组各项值即可。

#include <stdio.h>
#include <string.h>
int n; 
int ret[15];     //0-9 10个数字的出现次数 
int main()
{
	int i,temp;
	while(scanf("%d",&n)!=EOF)
	{
		memset(ret,0,sizeof(ret));
		for(i=1;i<=n;i++)
		{
			temp=i;
			while(temp!=0)
			{
				ret[temp%10]++;
				temp/=10;
			}
		} 	
		for(i=0;i<10;i++)
			printf("%d: %d\n",i,ret[i]);
		printf("\n");
	}
	return 0;
}
4. 子序列

输入两个字符串s和t,判断是否可以从t中删除0到多个字符(其他字符顺序不变)得到字符串s。例如abcde可以得到bce(删去2个字符),但无法得到dc。若可以则输出”Yes”; 否则输出”No”。(1<=lens<=lent<=100000,其中lens和lent分别为串s和t的长度)

样例输入:

sequence subsequence

person compression

VERDI vivaVittorioEmanueleReDiItalia

caseDoesMatter CaseDoesMatter

样例输出:

Yes

No

Yes

No

【分析】直接扫一遍两串即可。

#include <stdio.h>
#include <string.h>
#define maxn 100005
char s[maxn],t[maxn];
int main()
{
	int i,j;
	int lens,lent;
	int flag=0;
	while(scanf("%s %s",s,t)!=EOF)
	{
		i=0,j=0;
		flag=0;
		lens=strlen(s);
		lent=strlen(t);
		while(i<lent)
		{
			if(t[i]==s[j])
			{
				i++;
				j++;
			}
			else
				i++;
			if(j==lens)
			{
				flag=1;
				break;
			}
		}
		if(flag==1)
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

5. 周期串

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

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

样例输入:

abcabcabcabc

aaaa

样例输出:

3

abc

1

a

【分析】周期串定义:如果一个字符串是以一个或者一个以上的长度为k的重复字符串连接而成的,则该字符串为周期串,且周期为k。

一个周期串可能有多个周期,如”abcabcabcabc”,其周期为3,6,12,其中最小周期为3。

分析该问题时基本思路仍是枚举(这里是枚举k),记串长为len。接下来:

(1)如果len%k!=0,则k一定不是串的周期;

(2)否则,取子串[0, k)。将子串的第i个字符和输入的字符串的第i+k个字符比对,如果发现不相等,则k也一定不是串的周期,退出循环;

(3)如果循环正常结束,则说明k是串的周期,且是最小周期;子串[0, k}为最小周期串。

#include <stdio.h>
#include <string.h>
#define maxn 85
char str[maxn];
int main()
{
	int i,j;
	int k,flag;
	int minlen;
	while(scanf("%s",str)!=EOF)
	{
		k=1;
		minlen=strlen(str);
		while(k<=strlen(str))
		{
			if(strlen(str)%k!=0)   //k为最小周期时串长一定为k的整数倍,否则k不是str的周期 
			{
				k++;
				continue;
			}
			flag=1;       //flag标记k是否是str的周期 
			for(i=0;i<k;i++)
			{
				for(j=i+k;j<strlen(str);j+=k)
				{
					if(str[i]!=str[j])
					{
						flag=0;
						break;
					}
				}
			}
			if(flag==1)
				break;
			k++;
		}
		printf("%d\n",k);
		for(i=0;i<k;i++)
			printf("%c",str[i]);
		printf("\n");
	}
	return 0;
}

6. DNA序列

输入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

样例输入:

3

5 8

TATGATAC

TAAGCTAC

AAAGATCC

TGAGATAC

TAAGATGT

4 10

ACGTACGTAC

CCGTACGTAG

GCGTACGTAT

TCGTACGTAA

6 10

ATGTTACCAT

AAGTTACGAT

AACAAAGCAA

AAGTTACCTT

AAGTTACCAA

TACTTACCAA

样例输出:

TAAGATAC

7

ACGTACGTAA

6

AAGTTACCAA

12

【分析】贪心法。按列统计A. C. G. T四种字符的出现次数,将出现次数最多的字符保存在字符数组中。该字符数组(字符串)即到m个序列的Hamming距离和最小的DNA序列(最优解);然后求对应的距离时,只需遍历整个DNA序列,比较DNA序列的第i行第j列和最优解的第j列的字符,若不相同,tot+1,最后即可求得Hamming距离。

#include <stdio.h>
#include <string.h>
int T,m,n;
int t[4];      //记录A.C.G.T在每一列中的出现次数
char ch[4]={'A','C','G','T'};
char DNA[55][1005];  //保存输入的DNA序列
char ret[1005];      //保存最优解
int main()
{
    int i,j,k;
    int maxt,maxch,tot,len;   //maxt-字符最大出现次数 maxch-出现次数最多的字符 tot-最小Hamming距离 len-最优解长度
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&m,&n);
        for(i=0;i<m;i++)
            scanf("%s",DNA[i]);
        len=0;
        for(j=0;j<n;j++)      //按列统计A.C.G.T出现次数
        {
            memset(t,0,sizeof(t));
            for(i=0;i<m;i++)
            {
                if(DNA[i][j]=='A')
                    t[0]++;
                else if(DNA[i][j]=='C')
                    t[1]++;
                else if(DNA[i][j]=='G')
                    t[2]++;
                else if(DNA[i][j]=='T')
                    t[3]++;
            }
            maxt=t[0];         //求每一列出现次数最多的字符
            maxch=ch[0];
            for(k=1;k<4;k++)
            {
                if(t[k]>maxt)
                {
                    maxt=t[k];
                    maxch=ch[k];
                }
            }
            ret[len++]=maxch;
        }
        tot=0;
        for(k=0;k<n;k++)       //遍历DNA序列,求Hamming距离
        {
            for(i=0;i<m;i++)
            {
                if(ret[k]!=DNA[i][k])
                    tot++;
            }
        }
        ret[len]='\0';
        printf("%s\n",ret);
        printf("%d\n",tot);
    }
    return 0;
}

7.  谜题

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


样例输入:

TRGSJ
XDOKI
M VLN
WPABE
UQHCF
ARRBBL0
ABCDE
FGHIJ
KLMNO
PQRS
TUVWX
AAALLLL0
ABCDE
FGHIJ
KLMNO
PQRS
TUVWX
AAAAABBRRRLL0

样例输出:

Puzzle #1:
T R G S J
X O K L I
M D V B N
W P A E
U Q H C F
Puzzle #2:
 A B C D
F G H I E
K L M N J
P Q R S O
T U V W X
Puzzle #3:
This puzzle has no final configuration.

【分析】字符输入、输出、处理。需要特别注意读题!

(1)输入包含空格,因此考虑使用gets函数读取字符,并标记5*5网格中的空格坐标

(2)移动格子实为两个字符的交换,同时更新空格的坐标

(3)对于输入序列的合法性,在移动格子时,需要注意两点:

1°移动过程中发现空格坐标出界,则输入序列不合法

2°根据输入指令,移动过程中发现原空格周围(A-上B-下 L-左 R-右)的格子坐标出界,则输入序列不合法。

其它情况则表示输入合法。(可以添加标记变量实现上述过程)

#include <stdio.h>
#include <string.h>
char area[5][5];
char ch;
void Swap(char *a,char *b)
{
    char temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
int main()
{
    int i,j;
    int sx,sy,fail;           //fail记录处理序列是否有不合法的情况
    int ncase=1;
    while(gets(area[0]))      //注意输入包含空格
    {
        for(i=1;i<5;i++)
            gets(area[i]);
        for(i=0;i<5;i++)      //确定空格位置(横纵坐标)
        {
            for(j=0;j<5;j++)
            {
                if(area[i][j]==' ')
                {
                    sx=i;
                    sy=j;
                }
            }
        }
        fail=0;
        while((ch=getchar())!='0')    //处理输入序列
        {
            if(sx<0 || sy<0 || sx>=5 || sy>=5)  //空格出界,输入不合法
            {
                fail=1;
                break;
            }
            if(ch=='A')        //空格上/下/左/右的格子出界,输入不合法
            {
                if(sx-1<0)
                {
                    fail=1;
                    break;
                }
                Swap(&area[sx][sy],&area[sx-1][sy]);
                sx-=1;
            }
            else if(ch=='B')
            {
                if(sx+1>=5)
                {
                    fail=1;
                    break;
                }
                Swap(&area[sx][sy],&area[sx+1][sy]);
                sx+=1;
            }
            else if(ch=='L')
            {
                if(sy-1<0)
                {
                    fail=1;
                    break;
                }
                Swap(&area[sx][sy],&area[sx][sy-1]);
                sy-=1;
            }
            else if(ch=='R')
            {
                if(sy+1>=5)
                {
                    fail=1;
                    break;
                }
                Swap(&area[sx][sy],&area[sx][sy+1]);
                sy+=1;
            }
        }
        printf("Puzzle #%d: \n",ncase++);
        if(fail==0)
        {
            for(i=0;i<5;i++)
            {
                for(j=0;j<4;j++)
                    printf("%c ",area[i][j]);
                printf("%c\n",area[i][4]);
            }
        }
        else
            printf("This puzzle has no final configuration.\n");
    }
    return 0;
}

8.  纵横字谜的答案

A crossword puzzle consists of a rectangular grid of black and white squares and two lists of definitions (or descriptions). 

One list of definitions is for ``words" to be written left to right across white squares in the rows and the other list is for words to be written down white squares in the columns. (A word is a sequence of alphabetic characters.) 

To solve a crossword puzzle, one writes the words corresponding to the definitions on the white squares of the grid. 

The definitions correspond to the rectangular grid by means of sequential integers on ``eligible" white squares. White squares with black squares immediately to the left or above them are ``eligible." White squares with no squares either immediately to the left or above are also ``eligible." No other squares are numbered. All of the squares on the first row are numbered. 

The numbering starts with 1 and continues consecutively across white squares of the first row, then across the eligible white squares of the second row, then across the eligible white squares of the third row and so on across all of the rest of the rows of the puzzle. The picture below illustrates a rectangular crossword puzzle grid with appropriate numbering. 


An "across" word for a definition is written on a sequence of white squares in a row starting on a numbered square that does not follow another white square in the same row. The sequence of white squares for that word goes across the row of the numbered square, ending immediately before the next black square in the row or in the rightmost square of the row. 

A "down" word for a definition is written on a sequence of white squares in a column starting on a numbered square that does not follow another white square in the same column. The sequence of white squares for that word goes down the column of the numbered square, ending immediately before the next black square in the column or in the bottom square of the column. Every white square in a correctly solved puzzle contains a letter. 

You must write a program that takes several solved crossword puzzles as input and outputs the lists of across and down words which constitute the solutions. 


Input

Each puzzle solution in the input starts with a line containing two integers r and c (1 <= r <= 10 and 1 <= c <= 10), where r (the first number) is the number of rows in the puzzle and c (the second number) is the number of columns. The r rows of input which follow each contain c characters (excluding the end-of-line) which describe the solution. Each of those c characters is an alphabetic character which is part of a word or the character "*", which indicates a black square. The end of input is indicated by a line consisting of the single number 0. 


Output

Output for each puzzle consists of an identifier for the puzzle (puzzle #1, puzzle #2, etc.) and the list of across words followed by the list of down words. Words in each list must be output one-per-line in increasing order of the number of their corresponding definitions. The heading for the list of across words is "Across". The heading for the list of down words is "Down". In the case where the lists are empty (all squares in the grid are black), the Across and Down headings should still appear. 

Sample Input
2 2
AT
*O
6 7
AIM*DEN
*ME*ONE
UPON*TO
SO*ERIN
*SA*OR*
IES*DEA
0

Sample Output
puzzle #1:
Across
  1.AT
  3.O
Down
  1.A
  2.TO

puzzle #2:
Across
  1.AIM
  4.DEN
  7.ME
  8.ONE
  9.UPON
 11.TO
 12.SO
 13.ERIN
 15.SA
 17.OR
 18.IES
 19.DEA
Down
  1.A
  2.IMPOSE
  3.MEO
  4.DO
  5.ENTIRE
  6.NEON
  9.US
 10.NE
 14.ROD
 16.AS
 18.I
 20.A

【分析】字符串模拟题。题意:输入一个r行c列的网格(1<=r,c<=10),黑格用*号表示,每个白格都填有一个字母。如果一个白格的左边相邻位置或者上边相邻位置没有白格(可能是黑格,也可能出了网格边界),则称这个白格是一个起始格。首先把所有起始格从左到右,从上到下顺序编号1,2,3,…,要求找出所有横向单词。这些单词必须从一个起始格开始,向右延伸到一个黑格的左边或者整个网格的最右边。最后找出所有的竖向单词。

#include <stdio.h>
#include <string.h>
int r,c;
int sno[15][15];         //记录起始点编号(如果不是起始点记-1)
char area[15][15];       //保存网格字符
int main()
{
    int i,j;
    int num,temp,ncase=1;
    while(scanf("%d",&r)!=EOF)
    {
        if(r==0)
            break;
        scanf("%d",&c);
        for(i=0;i<r;i++)
            scanf("%s",area[i]);
        memset(sno,-1,sizeof(sno));
        num=1;
        for(i=0;i<r;i++)       //给所有起始格编号
        {
            for(j=0;j<c;j++)
            {
                if(area[i][j]=='*')   //当前格是黑格,直接返回
                    continue;
                if(j-1<0 || i-1<0)    //相邻位置出界
                    sno[i][j]=num++;
                else if((j-1>=0 && area[i][j-1]=='*') || (i-1>=0 && area[i-1][j]=='*'))    //当前格相邻位置不是白格
                    sno[i][j]=num++;
            }
        }
        printf("puzzle #%d:\n",ncase++);
        printf("Across\n");
        for(i=0;i<r;i++)        //找横向单词
        {
            j=0;
            while(j<c)
            {
                if(sno[i][j]!=-1)  //发现横向单词的起点后,向右找字母,直到碰到黑格或出界
                {
                    printf("%3d.%c",sno[i][j],area[i][j]);
                    while(j+1<c && area[i][j+1]!='*')
                    {
                        j++;
                        printf("%c",area[i][j]);
                    }
                    printf("\n");
                }
                j++;
            }
        }
        printf("Down\n");
        for(i=0;i<r;i++)        //找竖向单词
        {
            for(j=0;j<c;j++)
            {
                if(sno[i][j]==-1)
                    continue;
                else        //发现竖向单词的起点后,向下找字母,直到碰到黑格或出界
                {
                    printf("%3d.%c",sno[i][j],area[i][j]);
                    temp=i;             //因为要按起始格编号递增排序输出,因此使用temp保存起点行号并对其操作
                    sno[temp][j]=-1;    //随时"清空"
                    while(temp+1<r && area[temp+1][j]!='*')
                    {
                        temp++;
                        printf("%c",area[temp][j]);
                        sno[temp][j]=-1;
                    }
                    printf("\n");
                }
            }
        }
        printf("\n");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值