题三:18444 分数拆分
Description
输入正整数k(k<=1000),将1/k变为不少于2项,但不多于3项的1/(xi)之和,xi为正整数,且i表示序号 注:请使用long long
输入格式
多case,一行一个整数k 最后一行是0
输出格式
对每一个case,按等式最右边一项分母,由小到大排序输出满足条件的等式,最右边一项分母相同,则按最右边第二项,依次类推 每一个case完成后,输出一个空行(没有满足的等式时,也要输出该空行)
输入样例
2 3 4 0
输出样例
1/2=1/6+1/3 1/2=1/42+1/7+1/3 1/2=1/24+1/8+1/3 1/2=1/18+1/9+1/3 1/2=1/15+1/10+1/3 1/2=1/12+1/12+1/3 1/2=1/4+1/4 1/2=1/20+1/5+1/4 1/2=1/12+1/6+1/4 1/2=1/8+1/8+1/4 1/2=1/10+1/5+1/5 1/2=1/6+1/6+1/6 1/3=1/12+1/4 1/3=1/156+1/13+1/4 1/3=1/84+1/14+1/4 1/3=1/60+1/15+1/4 1/3=1/48+1/16+1/4 1/3=1/36+1/18+1/4 1/3=1/30+1/20+1/4 1/3=1/28+1/21+1/4 1/3=1/24+1/24+1/4 1/3=1/120+1/8+1/5 1/3=1/45+1/9+1/5 1/3=1/30+1/10+1/5 1/3=1/20+1/12+1/5 1/3=1/15+1/15+1/5 1/3=1/6+1/6 1/3=1/42+1/7+1/6 1/3=1/24+1/8+1/6 1/3=1/18+1/9+1/6 1/3=1/15+1/10+1/6 1/3=1/12+1/12+1/6 1/3=1/21+1/7+1/7 1/3=1/12+1/8+1/8 1/3=1/9+1/9+1/9 1/4=1/20+1/5 1/4=1/420+1/21+1/5 1/4=1/220+1/22+1/5 1/4=1/120+1/24+1/5 1/4=1/100+1/25+1/5 1/4=1/70+1/28+1/5 1/4=1/60+1/30+1/5 1/4=1/45+1/36+1/5 1/4=1/40+1/40+1/5 1/4=1/12+1/6 1/4=1/156+1/13+1/6 1/4=1/84+1/14+1/6 1/4=1/60+1/15+1/6 1/4=1/48+1/16+1/6 1/4=1/36+1/18+1/6 1/4=1/30+1/20+1/6 1/4=1/28+1/21+1/6 1/4=1/24+1/24+1/6 1/4=1/140+1/10+1/7 1/4=1/42+1/12+1/7 1/4=1/28+1/14+1/7 1/4=1/8+1/8 1/4=1/72+1/9+1/8 1/4=1/40+1/10+1/8 1/4=1/24+1/12+1/8 1/4=1/16+1/16+1/8 1/4=1/36+1/9+1/9 1/4=1/18+1/12+1/9 1/4=1/20+1/10+1/10 1/4=1/15+1/12+1/10 1/4=1/12+1/12+1/12
思路:
显然这是一个暴力枚举的问题,对于枚举而言,私以为大原则有一,关键有三。
大原则,简化问题,简化时间复杂度。
关键一:确定枚举对象
关键二:确定枚举范围
关键三:确定枚举条件,限制条件
最后再处理枚举过程中的细节问题,比如,乘法溢出问题,除法中的数据丢失问题,等等。
对于这道题,我们先简化分析他,对于两个的情况,1/x+1/y=1/ p。
那么我们就很轻松的可以想到,以其中一项为枚举对象, 寻找枚举的范围,来实现枚举。
接下来就是对于枚举对象的选择问题,从而寻早枚举的范围,设定x>=y>p;
我们通过放大原式,可以得到, 1/y+1/y >= 1/x+1/y = 1/p;即,2/y>=1/p;
所以,可得y的枚举范围[1,2p];那么,反过来,缩小原式是不可以实现找到枚举范围的。
最后再限制枚举的条件,同样,我们分析并转化原式1/x=1/p-1/y;即,x=p*y/(y-p);
那么,据题目要求,我们需要判定,x=p*y/(y-p)是不是整数,且y<=2*p。
这里有一个方法,对于除法柿子,我们只需要判断p*y%(y-p)?=0,即判断p*y是否整除(y-p);
那么三个项的情况,其实就可以转化为两个项的情况。对于,1 /x+1/y+1/z=1 /k;转化为->1/x+1/y= 1/k-1/z =1/p;同理我们设定,x>=y>=z>k
那么我们的枚举对象有两个,
第一个,先枚举z,利用已知的k,求得不同的p,判断,求出两个项的结果;
第二个,枚举y,利用求得的p,得到不同的x,判断,求出三个项的结果。
很自然的想到,我们的枚举范围是什么,同理,我们放大原式,得到z的范围,[1,3*k];y的范围[1,2*p];
对于第一个枚举对象,我们可以看作,对1/p+1/z=1/k的枚举,同上,我们需要判断,p=k*z/(z-k)是不是整数,且z<=2*k;
对于第二个枚举对象,我们看作z已经确定,1/x+1/y=1/p的枚举,同上我们需要判断,x=p*y/(y-p)是不是整数,且 z<=y<=2*p;
理论上到这里我们就写完了,但是显而易见的我们需要处理一些枚举过程中的细节问题,
1.题目提示我们使用long long
2.其中包含了大量的除法计算,会存在数据丢失(四舍五入)的问题。题中,1/p=1/k-1/z 即 p=k*z/(z-k) 的结果不一定是一个整数。会导致第二个枚举的计算结果出现错误,还有枚举范围的偏差。
对于数据丢失导致计算错误的问题,这里处理方式为,分离k*z/(z-k)的分子和分母。记t1=k*z,t2=z-k;
很简单的数学转换,将p*y%(y-p)?=0 转化为-> y * t1 % (y * t2 - t1) == 0; x=p*y/(y-p) 转化为-> x = y * t1 / (y * t2 - t1);
对于数据丢失导致的y枚举范围变化问题,我们可以将y的范围变为[1,2*p+1]解决。
简单证明:已知,p=k*z/(z-k)的结果小数位数值<0.5为出现数据丢失。
那么p其实一共有两种情况,小数位 <0.5 或者 小数位>=0.5;
1.设p=3.4,计算机中显示结果为3;令p+=0.5,p=3.9,计算机显示结果为4;防止丢失.
2.设p=3.6,计算机中显示结果为4;令p+=0.5,p=4.1,计算机显示结果为4;防止丢失.
那么y的范围可以写出[1,2*(p+0.5)];
综上,分析结束,我们就可以写出如下代码。
#include<iostream>
using namespace std;
long long k ;
void show(long long x,long long y,long long z) {
cout << "1/" << k << "=" << "1/" << x << "+" << "1/" << y << "+" << "1/" << z << endl;
}
void _show(long long y,long long z) {
cout << "1/" << k << "=" << "1/" << y << "+" << "1/" << z << endl;
}
int main() {
cin >> k;
while (k) {
long long x = 0, y = 0, z = 0, p = 0;;
for (z = k + 1; z <= 3 * k; z++) {
p = k * z / (z - k);
if (z <= 2 * k && k * z % (z - k) == 0) { y = k * z / (z - k); _show(y, z); }
//子母分离
long long t1 = k * z;
long long t2 = z - k;
for (y = p + 1; y <= 2 * p + 1; y++) {
if (y >= z && y * t1 % (y * t2 - t1) == 0) {
x = y * t1 / (y * t2 - t1);
//x = p * y / (y - p);
show(x, y, z);
}
}
}
cout << endl;
cin >> k;
}
}
写得较急,如有错误,请不吝赐教。