{ }codelper
最大公约数负数取余64 位整数P0094

分数相加并约分

每行以 p/q 的形式给出一个分数,共 n 个(分子 p 可能为负,分母 q 为正;输入的分数不一定是最简分数)。
把这些分数全部相加,以最简分数输出。
最简分数是指分子与分母的最大公约数为 1 且分母为正的形式,若为负数则负号放在分子上(例如 -1/12)。
如果和是整数(包括 0),只输出整数,不带 / 和分母。
第一行给出 n,接下来 n 行每行给出一个不含空格的分数。

输入
1 ≤ n ≤ 10,-100 ≤ p ≤ 100,1 ≤ q ≤ 30
输出
最简分数 a/b(b ≥ 2)或一个整数
样例 1 · stdin
3
1/2
1/3
1/6
stdout
1
样例 2 · stdin
2
-1/4
1/6
stdout
-1/12
main.py
输入(stdin)

运行时这些行会依次作为 input()/标准输入传入(运行中不会等待输入)。

$ 就绪

请在电脑上打开以编写和运行代码。在手机上可以查看题目和题解。

其他语言怎么写?查看各语言题解 · 8 种语言题解

把 p/q 加到目前的和 num/den 上时,用两个分母的最小公倍数 L = den / gcd(den, q) * q 通分:num = num * (L / den) + p * (L / q),den = L,并且每次都用 gcd(num, den) 约分。
如果只是把分母直接相乘,会增大到 30¹⁰ ≈ 6×10¹⁴,分子还要更大,在 JavaScript、TypeScript、Craykon 的数(2^53 以内精确)中就不再精确了。
即使通分,分母最大也有 lcm(1…30) ≈ 2.3×10¹²,所以 C、C++、Java、C# 需要 long/long long 而不是 int。
最大的陷阱是负数取余:C、C++、Java、C#、JavaScript、TypeScript、Craykon 的 % 跟随左边操作数的符号(-3 % 6 是 -3),所以手写的辗转相除法可能返回负的 gcd,用它去除会让分母变成负数(1/-12)。
因此在 gcd 里先取绝对值。
Python 的 % 跟随右边操作数的符号,math.gcd 和 C++17 的 std::gcd、std::lcm(<numeric>)总是返回非负值,没有这个问题。
Java、C#、C、JavaScript 没有内置的整数 gcd(Java 只有 BigInteger.gcd),要自己写。
Craykon 定义一个 最大公约数(a, b) 函数,并使用 数学.绝对值、数学.商(/ 是小数除法)。
输入的 p/q 用 Python split("/")、C scanf("%lld/%lld")、C++ cin >> p >> slash >> q、Craykon 分割("/") 拆开。
最后,结果为整数时写成 3、0,而不是 3/1、0/1。

Pythonmain.py · 13 行
from math import gcd n = int(input())num, den = 0, 1  # 지금까지의 합 num/den (늘 기약분수, den > 0)for _ in range(n):    p, q = map(int, input().split("/"))    lcm = den // gcd(den, q) * q  # 두 분모의 최소공배수로 통분    num = num * (lcm // den) + p * (lcm // q)    den = lcm    g = gcd(num, den)  # math.gcd는 음수를 받아도 0 이상을 돌려준다    num //= g    den //= gprint(num if den == 1 else f"{num}/{den}")
JavaScriptmain.js · 22 行
const lines = require("fs").readFileSync(0, "utf8").trim().split(/\r?\n/); // %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산const gcd = (a, b) => {  a = Math.abs(a);  b = Math.abs(b);  while (b !== 0) [a, b] = [b, a % b];  return a;}; const n = Number(lines[0]);let num = 0, den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)for (let i = 1; i <= n; i++) {  const [p, q] = lines[i].trim().split("/").map(Number);  const lcm = (den / gcd(den, q)) * q; // 두 분모의 최소공배수로 통분  num = num * (lcm / den) + p * (lcm / q);  den = lcm;  const g = gcd(num, den);  num /= g;  den /= g;}console.log(den === 1 ? `${num}` : `${num}/${den}`);
TypeScriptmain.ts · 23 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").trim().split(/\r?\n/); // %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산function gcd(a: number, b: number): number {  a = Math.abs(a);  b = Math.abs(b);  while (b !== 0) [a, b] = [b, a % b];  return a;} const n = Number(lines[0]);let num = 0, den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)for (let i = 1; i <= n; i++) {  const [p, q] = lines[i].trim().split("/").map(Number);  const lcm = (den / gcd(den, q)) * q; // 두 분모의 최소공배수로 통분  num = num * (lcm / den) + p * (lcm / q);  den = lcm;  const g = gcd(num, den);  num /= g;  den /= g;}console.log(den === 1 ? `${num}` : `${num}/${den}`);
JavaMain.java · 32 行
import java.util.Scanner; public class Main {    // %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산    static long gcd(long a, long b) {        a = Math.abs(a);        b = Math.abs(b);        while (b != 0) {            long t = a % b;            a = b;            b = t;        }        return a;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int n = Integer.parseInt(sc.nextLine().trim());        long num = 0, den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)        for (int i = 0; i < n; i++) {            String[] pq = sc.nextLine().trim().split("/");            long p = Long.parseLong(pq[0]), q = Long.parseLong(pq[1]);            long lcm = den / gcd(den, q) * q; // 두 분모의 최소공배수로 통분            num = num * (lcm / den) + p * (lcm / q);            den = lcm;            long g = gcd(num, den);            num /= g;            den /= g;        }        System.out.println(den == 1 ? String.valueOf(num) : num + "/" + den);    }}
C#Main.cs · 31 行
using System; class Program{    // %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산    static long Gcd(long a, long b)    {        a = Math.Abs(a);        b = Math.Abs(b);        while (b != 0) (a, b) = (b, a % b);        return a;    }     static void Main()    {        int n = int.Parse(Console.ReadLine());        long num = 0, den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)        for (int i = 0; i < n; i++)        {            string[] pq = Console.ReadLine().Trim().Split('/');            long p = long.Parse(pq[0]), q = long.Parse(pq[1]);            long lcm = den / Gcd(den, q) * q; // 두 분모의 최소공배수로 통분            num = num * (lcm / den) + p * (lcm / q);            den = lcm;            long g = Gcd(num, den);            num /= g;            den /= g;        }        Console.WriteLine(den == 1 ? $"{num}" : $"{num}/{den}");    }}
C++main.cpp · 21 行
#include <iostream>#include <numeric> int main() {    int n;    std::cin >> n;    long long num = 0, den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)    for (int i = 0; i < n; i++) {        long long p, q;        char slash;        std::cin >> p >> slash >> q;        long long lcm = std::lcm(den, q); // 두 분모의 최소공배수로 통분        num = num * (lcm / den) + p * (lcm / q);        den = lcm;        long long g = std::gcd(num, den); // std::gcd는 음수를 받아도 0 이상을 돌려준다        num /= g;        den /= g;    }    if (den == 1) std::cout << num << '\n';    else std::cout << num << '/' << den << '\n';}
Cmain.c · 33 行
#include <stdio.h>#include <stdlib.h> /* %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산 */static long long gcd(long long a, long long b) {    a = llabs(a);    b = llabs(b);    while (b != 0) {        long long t = a % b;        a = b;        b = t;    }    return a;} int main(void) {    int n;    scanf("%d", &n);    long long num = 0, den = 1; /* 지금까지의 합 num/den (늘 기약분수, den > 0) */    for (int i = 0; i < n; i++) {        long long p, q;        scanf("%lld/%lld", &p, &q);        long long lcm = den / gcd(den, q) * q; /* 두 분모의 최소공배수로 통분 */        num = num * (lcm / den) + p * (lcm / q);        den = lcm;        long long g = gcd(num, den);        num /= g;        den /= g;    }    if (den == 1) printf("%lld\n", num);    else printf("%lld/%lld\n", num, den);    return 0;}
Craykonmain.cray · 37 行
// %는 앞 수의 부호를 따라서 음수가 들어오면 음수 gcd가 나올 수 있다 → 절댓값으로 계산최대공약수(a, b){    a = 数学.绝对值(a);    b = 数学.绝对值(b);    当 (b != 0)    {        t = a % b;        a = b;        b = t;    }    返回 a;} n = 控制台.读行().转数字();num = 0;den = 1; // 지금까지의 합 num/den (늘 기약분수, den > 0)循环 (i = 0; i < n; i++){    pq = 控制台.读行().去空白().分割("/");    p = pq[0].转数字();    q = pq[1].转数字();    lcm = 数学.商(den, 최대공약수(den, q)) * q; // 두 분모의 최소공배수로 통분    num = num * 数学.商(lcm, den) + p * 数学.商(lcm, q);    den = lcm;    g = 최대공약수(num, den);    num = 数学.商(num, g);    den = 数学.商(den, g);}如果 (den == 1){    控制台.输出(num);}否则{    控制台.输出(num + "/" + den);}