网上虽然有很多解答,但不一定都对,这个是肯定对的。
本题是蓝桥杯的省赛题,题目原文如下
黄金分割数0.61803… 是个无理数,这个常数十分重要,在许多工程问题中会出现。有时需要把这个数字求得很精确。
对于某些精密工程,常数的精度很重要。也许你听说过哈勃太空望远镜,它首次升空后就发现了一处人工加工错误,对那样一个庞然大物,其实只是镜面加工时有比头发丝还细许多倍的一处错误而已,却使它成了“近视眼”!!
言归正传,我们如何求得黄金分割数的尽可能精确的值呢?有许多方法。
比较简单的一种是用连分数:f(n)=1/(1+f(n-1)) abs(f(n)-f(n-1))<10e-100
(连分数的图见QQ群)
这个连分数计算的“层数”越多,它的值越接近黄金分割数。
请你利用这一特性,求出黄金分割数的足够精确值,要求四舍五入到小数点后100位。
小数点后3位的值为:0.618
小数点后4位的值为:0.6180
小数点后5位的值为:0.61803
小数点后7位的值为:0.6180340
(注意尾部的0,不能忽略)
你的任务是:写出精确到小数点后100位精度的黄金分割值。
注意:尾数的四舍五入! 尾数是0也要保留!
显然答案是一个小数,其小数点后有100位数字。
注意:不要提交解答过程,或其它辅助说明类的内容。
说明一些函数的功能:
//find_first_not_of(‘0’); //顾名思义,找到第一个不是0字符的位置
//找到第一个不是0的位置之后,将这个位置后面的字符串拿走,因为这才是真正需要的
a = a.substr(find_first_not_of('0'));
作用:将一个字符串反转
reverse(a.begin(), a.end());
将一个字符串 0 加入到string a 的末尾
a.append("0");
这里有点像NULL,具体的含义作用稍后说明
string::npos
下面是函数功能的说明
string add(string a,string b){
a = a.substr(a.find_first_not_of('0'));
b = b.substr(b.find_first_not_of('0'));
long long lenA = a.length();
long long lenB = b.length();
long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
string ans(len,'0');//将答案初始化为00000000...
//core code core code core code core code core code
//将a拷贝到ans中
for(int i = 0; i < lenA; i++){
ans[i] = a[i];
}
//开始执行加法了
int temp = 0;//temp 是上一位相加的进位
for(int i = 0; i < len; ++i){
if(i < b.length())
temp += (ans[i] - '0') + (b[i] - '0'); //一位的加法
else
temp += (ans[i] - '0');
ans[i] = temp % 10 + '0'; //使数字又变回字符形式
temp /= 10;
}
//相加完之后翻转
reverse(ans.begin(),ans.end());
return ans.substr(ans.find_first_not_of('0'));
// core code core code core code core code core code core code core code
}
对这些代码进行说明,
首先,我们要做的是,让这些输入进来的数字变得美观,合适,方便我们运算
比如001,其实就是1,但二者其实都是合理合法的,只是前者不方便我们进行
加法运算而已,所以去掉前缀0,前缀(prefix),就变成了我们平常用的加法
a = a.substr(a.find_first_not_of('0'));
b = b.substr(b.find_first_not_of('0'));
其次,测量两个string 数的长度,翻译到数学上其实就是位数,我们确保两个数相加之后的len,肯定是lenA,lenB二者最大再加10位,其实这里多加了,是为了确保编程的方便,比如99+99 = 198 ,也就变成了3位,999+9999=10998,也就从4位变成了5位,依次类推,最多增加一个长度。
然后这些数字都是从小到大输入进来的,98754,而我们数组的储存是从低到高存储的,比如a[0] = 1,a[1] = 8, a[2] = 7…,这样不方便我们遍历。
因为我们要从个位数开始加起,我们平常的计算也是这样算的,所以再次反转,用不用担心呢,不用,我们算完,又会反转回来。而ans其实就是结果string,先初始化为0,
long long lenA = a.length();
long long lenB = b.length();
long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
string ans(len,'0');//将答案初始化为00000000...
首先,将a[]数组,拷贝到答案中,其实可以直接将a作为父体,因为我们手算是这样算的,但这样无法通过DevC++的编译,因为这里是值传递,而不是地址传递,所以拷贝一份吧
先定义一个temp,这个temp其实从始至终只是储存1位数字,最多2位,就是当下ans[i]和b[i]相加,所产生的数,如果是1位,就留着,2位,就要进位了
if(i < b.length())确保我们无论是把长度短的那个先加完,还是长度长的那个先加完,都是正确的,比如,123+12345,我们先把123(b)加完了,就会执行else语句,如果是12345(b)的话,那一直在if(i < b.length())中
ans[i] = temp % 10 + ‘0’ 的作用是,1位保留,2位就留个位,比如9%10 = 9,19%10 = 9
temp /= 10 这里的作用就是留下十位,用来准备下一次的进位
//将a拷贝到ans中
for(int i = 0; i < lenA; i++){
ans[i] = a[i];
}
//开始执行加法了
int temp = 0;//temp 是上一位相加的进位
for(int i = 0; i < len; ++i){
if(i < b.length())
temp += (ans[i] - '0') + (b[i] - '0'); //一位的加法
else
temp += (ans[i] - '0');
ans[i] = temp % 10 + '0'; //使数字又变回字符形式
temp /= 10;
}
对加法函数进行一个总结,就是将我们的竖式加法手工转化成代码,其实反映到现实中,计算机就是对我们的人工过程进行了虚拟重构,我们如果说现实中能够实现的,计算机绝大多数也能够实行,只要它是有穷的,可逻辑化,形式化的。
//此处默认,a一定大于b ,a - b,
string subtract(string a,string b){
//完整的减法中,a 可以小于b,这时结果为负数,交换ab即可
//1.翻转
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
//2.按位减法
//拷贝a到ans中
//string ans = a;
//这里写成了a.length()的话会出大麻烦
for(int i = 0; i < b.length(); i++){
if(a[i] >= b[i]){
a[i] = a[i] - b[i] + '0';//这一位上a>b
}else{ //这一位小了,要借位
int next = 1;
while(a[i + next] == '0') {
a[i+next] = '9';
next++;
}
//这里保证i + k这一位不是0,因为已经跳出了while循环
a[i+next] = a[i+next] - '1' + '0'; //重点语句,必须加上'0' ,这是借位
a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0'; //10是借位过来的,最后的+'0'使得重新成为了string
}
}
reverse(a.begin(), a.end());
if(a.find_first_not_of('0') == string::npos) return "0";//ans.find_first_not_of('0')有个特殊规则,本身是0的话,总不能把自己去掉
return a.substr(a.find_first_not_of('0'));
}
有些语句作用和加法一样,这里不再赘述
让我们回顾下,减法的过程,10000 - 9,第一个个位0,到第四个千位0,其实一直都是不够减的,所以一直都在借位,而且这个借位出现了传递性,明确了这个借位可以传递的思想,我们看代码
for(int i = 0; i < b.length(); i++){
if(a[i] >= b[i]){
a[i] = a[i] - b[i] + '0';//这一位上a>b
}else{ //这一位小了,要借位
int next = 1;
while(a[i + next] == '0') {
a[i+next] = '9';
next++;
}
//这里保证i + k这一位不是0,因为已经跳出了while循环
a[i+next] = a[i+next] - '1' + '0'; //重点语句,必须加上'0' ,这是借位
a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0'; //10是借位过来的,最后的+'0'使得重新成为了string
}
}
由于默认,b更小,所以遍历以b的长度为准,第一个if就是大白话
for(int i = 0; i < b.length(); i++){
if(a[i] >= b[i]){
a[i] = a[i] - b[i] + '0';//这一位上a>b
}
定义了一个next,即要向多少 更大位数,进行借位,如果下一位不是0,那自然可以借到位,比如 11 - 9,但如果下一位是0,自然要再借,比如201 - 9,
随后,就是记录借位操作了,
值得注意最后一行,a[i] = (a[i]-‘0’+10) - (b[i]-‘0’)+ ‘0’ 其中的10,其实就是从下一位借位过来的1,但权值更大的1.
else{ //这一位小了,要借位
int next = 1;
while(a[i + next] == '0') {
a[i+next] = '9';
next++;
}
//这里保证i + k这一位不是0,因为已经跳出了while循环
a[i+next] = a[i+next] - '1' + '0'; //重点语句,必须加上'0' ,这是借位
a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0'; //10是借位过来的,最后的+'0'使得重新成为了string
}
}
讲解下这个的作用,比如,9 - 9 = 0了,没错,我就要返回0,就是返回0,但是这明显和find_first_not_of(‘0’)的作用不相符,其实find_first_not_of本身会有这个机制判断,判断本身是不是要not_of的对象,如果是的话肯定会有错,那就返回空位置咯,nullposition简写不就是npos吗?
if(a.find_first_not_of('0') == string::npos)
int cmp(string a, string b){
if(a.find_first_not_of('0') == string::npos) a = '0';
else a.substr(a.find_first_not_of('0'));
if(b.find_first_not_of('0') == string::npos) b = '0';
else b.substr(b.find_first_not_of('0'));
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;
}
}
void i2s(int n, string &str){
stringstream stream;
stream <<n;
stream>>str;
}
//除法的本质是减法 ,默认a < b
string divide(string a,string b){
string ans = "0.";
for(int i = 0; i < 101; i++){
a.append("0");
int t = 0;
while(cmp(a,b) >= 0){ //a在append("0")之后 >= b 比如 1 / 2就是 (10 / 2) / 10, 10 / 2这时就变成减法
a = subtract(a,b);
t++;
}
string t_str;
i2s(t, t_str); //t是int,t_str是t的字符串形式
ans.append(t_str);
}
return ans;
}
首先,我们想想我们的除法是怎么计算的,5 / 2 = 2.5.对吧,其实也就是 50 / 2 = 25 (50够2减25次)再缩小10倍,就是先放大,再缩小,
那么a.append(“0”);就是在执行这个放大过程。
还有一个要分析的就是,这个连分数是和斐波那契数列有关的,这个大家手动的写一下就能够很快的推导出,或者参考下其它人的博客。
下面放出所有的代码
#include
#include
#include
#include
using namespace std;
string add(string a,string b);
string divide(string a,string b);
string subtract(string a,string b);
void i2s(int n, string &str);
int cmp(string a, string b);
//a是分子,b是分母
void i2s(int n, string &str){
stringstream stream;
stream <<n;
stream>>str;
}
int cmp(string a, string b){
if(a.find_first_not_of('0') == string::npos) a = '0';
else a.substr(a.find_first_not_of('0'));
if(b.find_first_not_of('0') == string::npos) b = '0';
else b.substr(b.find_first_not_of('0'));
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;
}
}
string add(string a,string b){
a = a.substr(a.find_first_not_of('0'));
b = b.substr(b.find_first_not_of('0'));
long long lenA = a.length();
long long lenB = b.length();
long long len = max(lenA,lenB) + 10;
//翻转,便于从低位逐步求和
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
string ans(len,'0');//将答案初始化为00000000...
//core code core code core code core code core code
//将a拷贝到ans中
for(int i = 0; i < lenA; i++){
ans[i] = a[i];
}
//开始执行加法了
int temp = 0;//temp 是上一位相加的进位
for(int i = 0; i < len; ++i){
if(i < b.length())
temp += (ans[i] - '0') + (b[i] - '0'); //一位的加法
else
temp += (ans[i] - '0');
ans[i] = temp % 10 + '0'; //使数字又变回字符形式
temp /= 10;
}
//相加完之后翻转
reverse(ans.begin(),ans.end());
return ans.substr(ans.find_first_not_of('0'));
// core code core code core code core code core code core code core code
}
//除法的本质是减法 ,a < b
string divide(string a,string b){
string ans = "0.";
for(int i = 0; i < 101; i++){
a.append("0");
int t = 0;
while(cmp(a,b) >= 0){ //a在append("0")之后 >= b 比如 1 / 2就是 (10 / 2) / 10, 10 / 2这时就变成减法
a = subtract(a,b);
t++;
}
string t_str;
i2s(t, t_str); //t是int,t_str是t的字符串形式
ans.append(t_str);
}
return ans;
}
//此处,a一定大于b ,a - b
string subtract(string a,string b){
//完整的减法中,a 可以小于b,这时结果为负数,交换ab即可
//1.翻转
reverse(a.begin(), a.end());
reverse(b.begin(), b.end());
//2.按位减法
//拷贝a到ans中
//string ans = a;
//这里写成了a.length()的话会出大麻烦
for(int i = 0; i < b.length(); i++){
if(a[i] >= b[i]){
a[i] = a[i] - b[i] + '0';//这一位上a>b
}else{ //这一位小了,要借位
int next = 1;
while(a[i + next] == '0') {
a[i+next] = '9';
next++;
}
//这里保证i + k这一位不是0,因为已经跳出了while循环
a[i+next] = a[i+next] - '1' + '0'; //重点语句,必须加上'0' ,这是借位
a[i] = (a[i]-'0'+10) - (b[i]-'0')+ '0'; //10是借位过来的,最后的+'0'使得重新成为了string
}
}
reverse(a.begin(), a.end());
if(a.find_first_not_of('0') == string::npos) return "0";//ans.find_first_not_of('0')有个特殊规则,本身是0的话,总不能把自己去掉
return a.substr(a.find_first_not_of('0'));
}
int n = 1000;
int main(){
string a = "1";
string b = "1"; //4 - 4 = 0; find_first_not_of('0') 会出错
string tmp;
//字符串模拟裴波那契
for(int i = 3; i < n; i++){
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; //0.占了2位
return 0;
}
0.61803398874989484820458683436563811772030917980576286213544862270526046281890244970720720418939113748
but这是有101位的,没有考虑四舍五入的,考虑四舍五入之后,末尾8要进位,
0.6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911375
才是正确答案。