2013年第四届蓝桥杯C/C++B组省赛题目及答案 1

一、高斯日记

大数学家高斯有个好习惯:无论如何都要记日记。

他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210

后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?

高斯出生于:1777年4月30日。

在高斯发现的一个重要定理的日记上标注着:5343,因此可算出那天是:1791年12月15日。

高斯获得博士学位的那天日记上标着:8113

请你算出高斯获得博士学位的年月日。

提交答案的格式是:yyyy-mm-dd, 例如:1980-03-21

请严格按照格式,通过浏览器提交答案。 注意:只提交这个日期,不要写其它附加内容,比如:说明性的文字。

解:

法一:用Excel解决

小技巧:利用Excel计算日期差
在这里插入图片描述
在这里插入图片描述
如上图,输入一个原始日期,在另一个单元格输入 “=” ,然后 鼠标点一下原始日期那个单元格,然后输入计算式,例如上图中是算+100天后的日期,再按一下回车就行了。但是这只支持1970年以后的日期。

回到题目,只能自己手动算:
开始之前有一个问题,1777年4月30日当天是算第一天还是从他的下一天才是第一天
这就要用到例子来判断,先假设1777年4月30日当天为第一天(通过验证例子得出是对的)
注意:一定要细心,最好用纸笔记着哪个数字是表示什么,有一个错了就全错

在这里插入图片描述
所以答案是:1799-07-16

法二:编程

解:简单枚举

#include <iostream>
using namespace std;

int main()
{
	int y = 1777, m = 4, d = 30;
	int days;
	cin >> days;
	for (int i = 1; i <= days-1; i++)
	{
		d++;
		if ((m == 1 || m == 3 || m == 5 || m == 7 || m == 8|| m == 10) && d == 32)
		{
			m++;
			d = 1;
			continue;
		}
		if ((m == 4 || m == 6 || m == 9 || m == 11) && d == 31)
		{
			m++;
			d = 1;
			continue;
		}
		if ( m == 2 && d == 29 && (!((y%4 == 0 && y%100 != 0) || y%400 == 0)))
		{
			m++;
			d = 1;
			continue;
		}
		if (m == 2 && d == 30 && ((y%4 == 0 && y%100 != 0) || y%400 == 0))
		{
			m++;
			d = 1;
			continue;
		}
		if(m == 12 && d == 32)
		{
			y++;
			m = 1;
			d = 1;
			continue;
		} 
	
	}
	cout << y << " - " << m << " - " << d << endl;
	return 0;
	
}

验证:
在这里插入图片描述
答案:
在这里插入图片描述

二、马虎的算式

小明是个急性子,上小学的时候经常把老师写在黑板上的题目抄错了。

有一次,老师出的题目是:36 x 495 = ?

他却给抄成了:396 x 45 = ?

但结果却很戏剧性,他的答案竟然是对的!!

因为 36 * 495 = 396 * 45 = 17820

类似这样的巧合情况可能还有很多,比如:27 * 594 = 297 * 54

假设 a b c d e 代表1~9不同的5个数字(注意是各不相同的数字,且不含0)

能满足形如: ab * cde = adb * ce 这样的算式一共有多少种呢?

请你利用计算机的优势寻找所有的可能,并回答不同算式的种类数。

满足乘法交换律的算式计为不同的种类,所以答案肯定是个偶数。

答案直接通过浏览器提交。 注意:只提交一个表示最终统计种类数的数字,不要提交解答过程或其它多余的内容。

解:简单枚举

#include <stdio.h>

int main()
{
	int ans = 0;
	for (int a = 1; a < 10; a++)
	{
		for (int b = 1; b <10; b++)
		if (b != a)
		{
			for (int c = 1; c <10; c++)
			{
				if (c != a && c!= b)
				{
					for (int d = 1; d <10; d++)
					if (d != a && d!= b && d!= c)
					{
						for (int e = 1; e <10; e++)
						if (e != a && e!= b && e!= c && e!=d)
						{
							//ab * cde = adb * ce
							if ((a*10+b) * (c*100+d*10+e) == (a*100+d*10+b) * (c*10+e))
							{
								ans++;
								//如果不放心,可以输出以便手动计算验证 
								printf("(%d*10+%d) * (%d*100+%d*10+%d) == (%d*100+%d*10+%d) * (%d*10+%d)\n",a,b,c,d,e,a,d,b,c,e,(a*10+b) * (c*100+d*10+e));
							}
						}
					}
				}
			}
		}
	}
	printf("%d",ans);
} 

在这里插入图片描述
答案:142

三、第39级台阶

小明刚刚看完电影《第39级台阶》,离开电影院的时候,他数了数礼堂前的台阶数,恰好是39级!

站在台阶前,他突然又想着一个问题:

如果我每一步只能迈上1个或2个台阶。先迈左脚,然后左右交替,最后一步是迈右脚,也就是说一共要走偶数步。那么,上完39级台阶,有多少种不同的上法呢?

请你利用计算机的优势,帮助小明寻找答案。

要求提交的是一个整数。 注意:不要提交解答过程,或其它的辅助说明文字。

解:
模式匹配法:相似问题到现有问题
这道题的母题就是斐波拉契数列计算f(n) = f(n-1) + f(n-2)
这样想:设目前剩下走的台阶有n级,第一步迈一级台阶,往后有n-1级台阶的迈法;第一步迈两级台阶,往后有n-2级台阶的迈法,以此类推。总迈法就是f(n-1) + f(n-2)

#include <iostream>
using namespace std;
#define N 39
int ans = 0;

//先去掉一个条件:要求走偶数步。 如果只看一次迈一个或两个台阶, 一共有多少种走法?
//那就是斐波拉契数列,求第39项
//f(n = 39) { return f(n-1)+f(n-2) } 

// n是剩余要走的台阶 ,step是已走的步数 
void f(int n,int step)
{
	if (n<0)
	{
		return;
	}
	if (n == 0 && step%2 == 0)
	{
		ans++;
		return;
	}
	f(n-1,step+1);
	f(n-2,step+1);
}

int main()
{
	f(39,0);
	cout << ans << endl;
	return 0;
} 

在这里插入图片描述
答案:51167078
(注:这个方法不是最有效率的,但这只是个填空题,用这样的思路想比较简单)

四、黄金连分数

黄金分割数0.61803… 是个无理数,这个常数十分重要,在许多工程问题中会出现。有时需要把这个数字求得很精确。

对于某些精密工程,常数的精度很重要。也许你听说过哈勃太空望远镜,它首次升空后就发现了一处人工加工错误,对那样一个庞然大物,其实只是镜面加工时有比头发丝还细许多倍的一处错误而已,却使它成了“近视眼”!!

言归正传,我们如何求得黄金分割数的尽可能精确的值呢?有许多方法。

比较简单的一种是用连分数:
在这里插入图片描述
这个连分数计算的“层数”越多,它的值越接近黄金分割数。

请你利用这一特性,求出黄金分割数的足够精确值,要求四舍五入到小数点后100位。

小数点后3位的值为:0.618
小数点后4位的值为:0.6180
小数点后5位的值为:0.61803
小数点后7位的值为:0.6180340
(注意尾部的0,不能忽略)

你的任务是:写出精确到小数点后100位精度的黄金分割值。

注意:尾数的四舍五入! 尾数是0也要保留!

显然答案是一个小数,其小数点后有100位数字,请通过浏览器直接提交该数字。 注意:不要提交解答过程,或其它辅助说明类的内容。

解:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/* 
1、转为求斐波那契数列的n和n+1项
2、n取多少?答:直到再增加n,小数点后的101位(因为要四舍五入)没有变化
3、不能用c语言定义的整数型直接运算,要手工地写大数加法和除法(实际上是减法)  */ 
#include <iostream>
#include <string>
#include <algorithm>
#include <sstream>
using namespace std;

int n = 50;

//大数的加减乘除计算的模板,最好能背下来,要用到的时候可以直接敲出来 
//下面的计算函数是针对本题的,并不完整 

void i2s(int num, string &str)
{
	stringstream ss;
	ss << num;
	ss >> str;
}

string add(string a,string b)
{
	a = a.substr(a.find_first_not_of('0')); //substr:复制子字符串;find_first_not_of:与()中指定的字符串中任意一个字符都不相符的字符的位置地址
	b = b.substr(b.find_first_not_of('0')); //此时得到两串开头没有0的新字符串
	long long lenA = a.length();
	long long lenB = b.length();
	long long len = max(lenA, lenB) + 10; //找这两个之间最大长度的那个。
	//因为有可能a和b的长度相同,最后两个相加了之后要进一位,所以最大长度应该是这两个中的最大长度+1
	
	//翻转,便于从低位逐步求和 
	reverse(a.begin(), a.end());  // reverse是<algorithm>的函数,头文件已经包了
	reverse(b.begin(), b.end());
	
	string ans(len, '0'); //初始化答案为len长,全部为字符0,方便计算 
	//把a拷贝到ans中: 
	for(int i = 0; i < lenA; i++)
	{	
		ans[i] = a[i];
	}
	int tmp = 0; //tmp是上一位相加后的进位 
	for (int i = 0; i < len; i++) //跑一下整个len的长度
	{	
		if (i < b.length())	
			tmp += (ans[i] - '0') + (b[i] - '0');  //-‘0’:转化成数字  //假设为18 
		else	
			tmp += (ans[i] - '0');
		//tmp最多是两个9相加得 18,此时就要进位,然后ans[i]位只留个位数,把进位放到tmp 
		ans[i] = tmp % 10 + '0'; //8  8+'0' //假如上一行算得18,此时ans[i]=8,要把数字转回字符串
		tmp /= 10; //按假设,则此时tmp=1 
	}
	//要返回的时候要再翻转
	reverse(ans.begin(), ans.end());
	return ans.substr(ans.find_first_not_of('0'));
}

int cmp(string a, string b)
{	//在本题情况下,补0都是在后面补的 
	unsigned long i1 = a.find_first_not_of('0');
	if (i1 == string::npos) a = '0';
	else a.substr(i1);
	
	unsigned long i2 = b.find_first_not_of('0');
	if (i2 == string::npos) b = '0';
	else b.substr(i2);
	
	if (a.length() > b.length()) return 1;
	else if (a.length() < b.length()) return -1;
	else
	{ //长度相等 
		if (a < b) return -1;
		if (a > b) return 1;
		else return 0;
	}
}

//此处,a一定大于等于b 
string subtract(string a, string b)
{
	//完整的减法里面呢,a可以小于b,这时结果为负数,交换a b进行下面的代码 
	// 但是本题这里已经可以确定a一定大于等于b了(不是的话也不会进入while执行这个函数
	//1.翻转 
	reverse(a.begin(), a.end()); 
	reverse(b.begin(), b.end());
	//2.按位做减法 
	for (int i = 0; i < b.length(); i++)
	{	
		if (a[i] >= b[i])
		{
			a[i] = a[i] - b[i] + '0'; //要加'0'转回string
		}
		else  //a<b,就没办法直接减,要借位(向后借)
		{
			int k = 1; //k为向后借的位数(因为不能确定它的下一位能不能借不能就再下一位,类推) 
			while (a[i+k] == '0') //想要借位的那个数是0的话,就没法借,要再向下一位借,而借完之后这一位就变成9
			{
				a[i+k] = '9';  
				k++;
			}
			//这里可以保证i+k这一位上不是0 (因为是0的话就还要进while)
			a[i+k] = a[i+k] - '1' + '0'; //a[i+k]和'1'两个都是字符,他们的差就是他们在ASCII码上的差值,是一个数字,所以要+'0'转化回字符 
			
			a[i] = (a[i] - '0' + 10) - (b[i] - '0') + '0'; // -'0':把string变成int,+'0':把int变成string。变成整数来计算,算完的结果变成字符来保存 
		}
	}
	reverse(a.begin(), a.end());
	if (a.find_first_not_of('0') == string::npos)  return "0"; //从头到尾都找不到非0(a.find_first_not_of('0')是非0数) (string::npos如果作为一个返回值则表示没有找到匹配项)
	//如果没有这个if,当a为0时,a.find_first_not_of('0')会取0的下一个,就会越界 
	return a.substr(a.find_first_not_of('0')); //从第一个非0字符开始往后截取   
}

//除法转换成减法
string divide(string a, string b)
{
	//只考虑a<b的情况(因为我们根据推出的斐波拉契就知道a是小于b的) 
	string ans = "0."; //因为a<b,a/b得出的肯定是0点几
	//转化成减法 
	for (int i = 0; i < 101; i++)
	{ //101次 
		a.append("0"); //每次在后面补0
		int t = 0;
		while (cmp(a,b) >= 0) //a>b或a=b,都要做减法,就算是a=b,也要再减一次 (如果不满足,就会回去补0) 
		{ //ps:a=b的话就表示除尽了,但在本题中是不会出现这种情况的,因为是无限不循环小数  
			a = subtract(a,b); //不停地做减法
			t++; //记录做了多少次减法 
		}
		string t_str;
		i2s(t,t_str); //把t改成string 
		ans.append(t_str);
	}
	return ans;
} 

int main()
{
	//在本题中,a一定是小于b的 
	string a = "1";
	string b = "1";
	
	for (int i = 3; i <= n; i++)
	{
		string tmp = b;
		b = add(a,b);
		a = tmp;
	}
	//a、b是斐波那契的第n-1项和第n项 
	string ans = divide(a,b);
	cout << ans << endl;
	cout << ans.length()-2 << endl;  //看小数部分是不是101位 
	return 0;
}

n不够大时,前100位数是不稳定的,下面要不断增大n,直到试出输出不变为止:

n=50时,输出为:0.61803398874989484820740990001204904326284254042472288566070913139408284656461170787663185198760678802

n=100时,输出为:0.61803398874989484820458683436563811772031274396379568575359185108829019869887522987627156252996318428

n=200时,输出为:0.61803398874989484820458683436563811772030917980576286213544862270526046281890244971288825799042314041

n=300时,输出为:0.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748

n=400时,输出为:0.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748

此时可看到数据已经稳定了

所以答案是(记得要四舍五入):0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375

五、前缀判断

如下的代码判断 needle_start指向的串是否为haystack_start指向的串的前缀,如不是,则返回NULL。

比如:“abcd1234” 就包含了 “abc” 为前缀

char* prefix(char* haystack_start, char* needle_start)
{
    char* haystack = haystack_start;
    char* needle = needle_start;

    
    while(*haystack && *needle){
        if(______________________________) return NULL;  //填空位置
    }
    
    if(*needle) return NULL;
    
    return haystack_start;
}

请分析代码逻辑,并推测划线处的代码,通过网页提交。 注意:仅把缺少的代码作为答案,千万不要填写多余的代码、符号或说明文字!!

ps:遇到代码填空题,先把代码复制到编译器,加上头文件,让他可以编译

解:

#include <iostream>
using namespace std;

//haystack_start 母串
//needle_start  前缀 

char* prefix(char* haystack_start, char* needle_start)
{
    char* haystack = haystack_start;
    char* needle = needle_start; //前缀 

    
    while(*haystack && *needle){ //两个指针都没有越界 (指针+*表示指针指向的内容,如果越界,会返回false) 
        //if(______________________________) return NULL;  //填空位置
        //需要移动并判断
		if (*(haystack++) != *(needle++)) return NULL;  //先*取了内容才到++指针往下一位移动 
		//就是两个字符串逐个字符往下比较,直到比完都没有不相等的(needle_start是前缀,长度肯定<=haystack_start,肯定是needle_start先比完)
		//needle_start都比完了也没有不相等的说明needle_start是haystack_start的前缀,返回haystack_start 
		//反之,没有越界(说明没比完)的时候就有不相等的,needle_start就不是前缀,返回NULL 
    }
    
    if(*needle) return NULL;
    
    return haystack_start;
}

int main()
{
	cout << prefix("abc123","abc") << endl;
	return 0;
}

答案:*(haystack++) != *(needle++)

猜你喜欢

转载自blog.csdn.net/weixin_45550460/article/details/105271908