【问题描述】
已知 S 是一个小于 1 的循环小数,请计算与 S 相等的最简真分数是多少。
例如 0 . 3333 · · · 等于 1/3
,0 . 1666 · · · 等于 1/6。
【输入格式】
输入第一行包含两个整数 p 和 q,表示 S 的循环节是小数点后第 p 位到第q 位。
第二行包含一个 q 位数,代表 S 的小数部分前 q 位。
【输出格式】
输出两个整数,用一个空格分隔,分别表示答案的分子和分母。
【样例输入】
1 6
142857
【样例输出】
1 7
【评测用例规模与约定】
对于所有评测用例,1 ≤ p ≤ q ≤ 10。
思路(从老师那偷学习来的):
大体上,求相除能得到这个循环小数的那两个正整数,然后约分,答案就能出来了。
所以我们的主要任务就变成了如何求出那两个正整数。
这里面有一个数学的小知识即:任意整数除以与这个整数位数相同且全由9组成的数,就能得到一个循环小数。
比如,12是个两位数,除于和它相同位数且都由9组成的数就是 12 / 99。结果就是0.121212......循环小数。其他的位数依次类推。
所以,如果这里明确的告诉我们给定的数据就是循环小数的循环节(指的是循环小数中循环的那几位)。那问题就变简单了,我们只用把这个数字存储然后求和它等位由9组成的数,然后约分,答案就出来了。
但是,题目里数据中会出现一部分是不循环的小数,一部分是循环的小数。
举个例子:
题目输入
3 6
123456
这就代表从第三位开始也就是3456为一个循环节,小数为0.12 3456 3456 3456......循环下去。
这该怎么处理呢?
不要把它们放在一起思考!以上面为例子,我们可以把这个循环小数的不循环位数和循环位数分开当成0.12 + 0.003456 3456.....来做。这样问题就和上文提到的求相除得到一个循环小数的那两个正整数是一样的了。
我们建立两个数组,一个数组中两个元素,用来存储相除能得到这两个小数的整数。
int qd , zd;//循环节开始位数和循环节结束位数 scanf("%d%d" , &qd , &zd);long long bxh[2];//存储不循环部分 shuru(bxh , qd - 1);//求那两个整数 long long xh[2];//存储循环部分 shuru(xh , zd - qd + 1);//同上
如何求出整数
对于不循环的部分,它们是确定的一定是 某个数 / 比这个数大一位数整百数
以上面为例子0.12 就能表示为12 / 100.
对于循环的部分,暂时先对它们也是和上面一样的处理,0.003456 3456......在这次处理之后能变成3456 / 10000.发现了没有,这样会变成0.3456,不仅没有循环,而且还少了两位0.这个问题我们后续会继续处理。
void shuru(long long sz[] , int ws)
{sz[0] = 0LL;//这个数代表分子 sz[1] = 1LL;//这个数代表分母 while(ws -- > 0)//取多少位 {long long temp;scanf("%1lld" , &temp);//读入一个整数 sz[0] *= 10LL;sz[0] += temp;//加上分子 sz[1] *= 10LL;//代表整数的位数 }
}
如何处理
我们从简单的出发
首先我们要的是循环小数,那么我们的分母就必须表示成和它的分子等位的9组成的数,这边我们有的是比分子多一位的整百,所以在这个基础上减一就行了。
10000 - 1 = 9999.不多不少,刚刚好。
接着来处理那两个消失的0(。
往后走两位,相当于除以100.公式表示就是 (3456 / (10000 - 1))/ 100。这样
0.003456 3456.....就用了两个整数表示出来了
代码奉上
void xunhuan_houyi(long long sz[] , int wy)//sz表示存储的那两个整数,wy表示位移(WeiYi)的位数,就是上文讲的,消失的0
{sz[1] --;//分母首先减一 while(wy -- > 0){sz[1] *= 10;//因为是整体除以100,所以相当于分母乘以100(记得举一反三,不要被这个例子限制住了) }
}
接下来的工作
就是把这两部分合并起来,然后约分就完事了
void xiangjia(long long sz1[] , long long sz2[])//sz1是不循环部分的整数表示,sz2是循环部分的整数表示
{sz1[0] = sz1[0] * sz2[1] + sz2[0] * sz1[1];sz1[1] *= sz2[1];long long gys = gcd(sz1[0] , sz1[1]);//约分,求最大公约数,用的是辗转相除法printf("%lld %lld\n" , sz1[0] / gys , sz1[1] / gys);
}
理解这段代码可能会有些困难。
如何合并
不要想成那种具体小数之间的合并,把它理解成分数的合并。
一上面的为例
12 / 100 + (3456 / 9999)/100;
12 / 100 + (3456 / 999900);//把100合并进去
此时这种状态才是当前这两个数组里存储的数
即不循环部分存储12和100;循环部分存储3456和999900.
然后通分
(12*999900)/(100*999900)+(3456*100)/(100*999900);
即合并后分子为12*999900 + 3456 * 100
这就相当于不循环部分分子乘以循环部分分母,循环部分分子乘以不循环部分分母
合并后分母为100*999900
简单的通分。
然后求最大公约数,求出后直接输出就行了。
完整代码奉上
#include <stdio.h>
void shuru(long long [] , int);
void shuchu(long long []);
void xunhuan_houyi(long long [] , int);
void xiangjia(long long [] , long long []);
long long gcd(long long , long long);
int main (void)
{int qd , zd;//循环节开始位数和循环节结束位数 scanf("%d%d" , &qd , &zd);long long bxh[2];//存储不循环部分 shuru(bxh , qd - 1);//求那两个整数 long long xh[2];//存储循环部分 shuru(xh , zd - qd + 1);//同上 xunhuan_houyi(xh , qd - 1);xiangjia(bxh , xh);return 0;
}long long gcd(long long q , long long h)
{return q % h ? gcd(h , q % h) : h;
}void xiangjia(long long sz1[] , long long sz2[])//sz1是不循环部分的整数表示,sz2是循环部分的整数表示
{sz1[0] = sz1[0] * sz2[1] + sz2[0] * sz1[1];sz1[1] *= sz2[1];long long gys = gcd(sz1[0] , sz1[1]);printf("%lld %lld\n" , sz1[0] / gys , sz1[1] / gys);
}void xunhuan_houyi(long long sz[] , int wy)
{sz[1] --;//分母首先减一 while(wy -- > 0){sz[1] *= 10;//因为是整体除以100,所以相当于分母乘以100(记得举一反三,不要被这个例子限制住了) }
}void shuchu(long long sz[])
{printf("%lld %lld\n" , sz[0] , sz[1]);
}void shuru(long long sz[] , int ws)
{sz[0] = 0LL;//这个数代表分子 sz[1] = 1LL;//这个数代表分母 while(ws -- > 0)//取多少位 {long long temp;scanf("%1lld" , &temp);//读入一个整数 sz[0] *= 10LL;sz[0] += temp;//加上分子 sz[1] *= 10LL;//代表整数的位数 }
}