PTAL1-009 N个数求和(20 分)最全最通俗的解析

本题的要求很简单,就是求N个数字的和。麻烦的是,这些数字是以有理数“分子/分母”的形式给出的,你输出的和也必须是有理数的形式。
输入格式:
输入第一行给出一个正整数N(<=100)。随后一行按格式“a1/b1 a2/b2 ...”给出N个有理数。题目保证所有分子和分母都在长整型范围内。另外,负数的符号一定出现在分子前面。
输出格式:
输出上述数字和的最简形式 —— 即将结果写成“整数部分 分数部分”,其中分数部分写成“分子/分母”,要求分子小于分母,且它们没有公因子。如果结果的整数部分为0,则只输出分数部分。
输入样例1:
5
2/5 4/15 1/30 -2/60 8/3
输出样例1:
3 1/3
输入样例2:
2
4/3 2/3
输出样例2:
2
输入样例3:
3
1/3 -1/6 1/8
输出样例3:

7/24

此题不算很难,关键在于方法的选用和细节的处理,如果方法不当,则会出现一些数据上的错误,如果有些细节注意不到,会被坑很长时间才能爬上来。本人做此题的时候30min就提交了,拿了17分,硬是卡在一个3分的测试点上过不了,找坑找了1个小时,AC,总共花了92min。。原因就是负数这个细节没有注意到和处理好。那么负数这个细节具体又如何处理得当呢?我们先来讲求出和的方法。

有些方法可能是先将分母全部乘起来,得到分母的最大公倍数,再求出这个最大公倍数和每个分母的比值,乘以每个分子,再把每个分子相加,再求分子分母的最大公约数,分子分母分别除以它。本人之前做过类似的一道题,求的是有理数的均值,用这个方法结果出现一些数据上的错误,后来用long long类型通过了。本人认为,此方法效率较低,对数据类型有一定依赖,步骤略繁,不是一个好的方法。那么,是否可以只用int类型,就能通过那到题呢?本人当时就用了一种方法,就是每两项通分,得到一个通分项,再将这个项和后一项通分得到一个项,比如有3项,我将前两项通分,通分得到的项再与第3项通分,如果有N项,两两通分,以此类推。有点类似于动态规划的方法,从一些基本的项开始一步步迭代上去。得到最后通分的项后,再求最大公约数,约分,这样能在用int类型的情况下AC那题。故用这个方法解此题可以用int,也可以用long。

有了好的方法,你已经能得到大部分分数了。如果要AC,那么必须注意负数的情况。题目要求是负号一定写在前面,于是我们可以知道,输入的时候,分子可正可负,分母必定是正的。故用辗转相除法求的最大公约数也必定是正的,具体原因请看我之后的代码,所以分子分母原来的正负情况和约分后的一样,这点需要注意,本人当时想错了,以为约分后分子分母的正负情况改变,于是引发错误。但是别以为如果分子是负数,负号本身就输出在前面,就不用仔细推敲其他可能的情况了。因为你还需要处理输出整数 分数的形式这个情况。

本人此题用的方法是,如果分子大于分母,用一个循环递减分子,如果递减后的分子能被分母整除,那么输出分子相对分母的倍数。你是否注意到,如果分子是负数,你递减只会离分母越来越远,所以为了统一处理递减分子这个操作,你必须先判定分子是否为负数,如果是则将分子乘以-1,输出倍数的时候在前面加上负号才行。你可能会说你不用递减这个操作,用其他的方法,但是你在判定分子是否大于分母的时候已经需要判定分子是否为负数了,而且这个判定是必需的。那么为了统一操作,我们可以在约分完分数后,引入一个判定分子是否为负数的标识,如果分子是负数,则分子乘以-1,在之后的操作中判定这个标识来决定是否输出负号。

分析完方法和细节,再加以实现,此题就能AC了,以下是具体代码:

#include<iostream>
using namespace std;
int main()
{
	int N;
	cin>>N;
	long *a=new long[N];
	long *b=new long[N];
	char c;
	for(int i=0;i<N;i++)
	cin>>a[i]>>c>>b[i];
	long numr=a[0],deno=b[0];
	for(int i=1;i<N;i++)
	{//每两项通分,迭代 
		numr=numr*b[i]+a[i]*deno;
		deno*=b[i];
	}	
	if(numr==0)//如果分子和为0,则输出0,退出 
	{
		cout<<0;
		return 0;
	}
	long A=numr,B=deno,R;
	while(B)//求最大公约数 
	{
		R=A%B;
		A=B;//注意,分母B一定是正数,故A即最大公约数也必定是正数 
		B=R;
	}//故若未约分前分子为负数,分母为正数,则经过约分,分子还是负数,分母还是正数 
	numr/=A;
	deno/=A;
	int is_negative=0;//定义负数标识 
	if(numr<0)//若分子小于0 
	{
		numr*=-1;
		is_negative=1;//标识 
	}
	if(numr>=deno)//若分子不小于分母 
	{
		if(deno==1)//若分母为1 
		{
			if(is_negative)//如果分子为负数,输出负号,下同 
			cout<<'-';
			cout<<numr;
		}
		else
		{
			for(int i=1;;i++)
			if((numr-i)%deno==0)//如果分子大于分母,寻找能被分母整除的数 
			{
				cout<<(numr-i)/deno<<' ';//找到,输出此数相对于分母的倍数 
				if(is_negative)
				cout<<'-';
				cout<<i<<'/'<<deno;//输出此数和原分子的差,即现在的分子,输出原分母 
				break;
			}
		}
	}
	else//若分子小于分母,输出分数 
	{
		if(is_negative)
		cout<<'-';
		cout<<numr<<'/'<<deno;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_37729102/article/details/80713906