{ }codelper
取余负数取余64 位整数P0266

每个轮子格数不同的计数器

一台旧的机械计数器上有 n 个轮子排成一行。
从左数第 i 个轮子有 0 到 b_i - 1 的格,现在指着第 d_i 格。
把计数器向前转一格,最右边的轮子加 1。
某个轮子从 b_i - 1 再走一格就回到 0,它左边的轮子加 1。
最左边的轮子从 b_1 - 1 回到 0 时,整个计数器向前翻过了一次。
向后转一格则反过来:最右边的轮子减 1,从 0 再走一格变成 b_i - 1,同时它左边的轮子减 1。
最左边的轮子从 0 变成 b_1 - 1 时,整个计数器向后翻过了一次。
t 为正时向前转 t 格,为负时向后转 -t 格,为 0 时不转。
第一行给出 n,第二行给出 b_1 … b_n,第三行给出 d_1 … d_n,第四行给出 t。
同一行的数用一个空格分开。
第一行从左边的轮子起输出转完后各轮子指着的格,用一个空格分开。
第二行输出整个计数器翻过的次数。
向前翻过就写次数,向后翻过就在次数前加 -,没有翻过就写 0。

输入
1 ≤ n ≤ 10,2 ≤ b_i ≤ 60,0 ≤ d_i < b_i,-10^12 ≤ t ≤ 10^12(都是整数)
输出
第一行:n 个轮子的格;第二行:翻过的次数(向后为负)
样例 1 · stdin
3
24 60 60
23 59 50
15
stdout
0 0 5
1
样例 2 · stdin
2
7 3
0 1
-5
stdout
5 2
-1
main.py
输入(stdin)

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

$ 就绪

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

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

从最右边的轮子开始,把进来的值 s = d +(传进来的值)分成格 s mod b 和传给左边的商即可。
向后转时 s 是负数,各语言的 % 和 / 就不同了。
Python 的 %、// 是向下取整除法,格总在 0 ~ b-1,商也是向下取整的值(-4 // 3 == -2),直接就对。
C、C++、Java、C#、JS、TS、Craykon 的 % 跟随被除数的符号(-4 % 3 == -1),C、C++、Java、C# 的整数 /、JS/TS 的 Math.trunc(s / b) 和 Craykon 的 数学.商 向 0 截断(-4 / 3 == -1),于是格变成负数,借位也少了一个。
JS、TS、Craykon 的 / 会除出小数(-4 / 3 == -1.333…),不能直接当商用。
先用 (s % b + b) % b 修正格,再用 (s - 格) / b 求商(正好整除),或者在 Java 里用 Math.floorMod、Math.floorDiv。
只修正格、商仍用向 0 截断的除法也是常见错误。
第二个陷阱是大小。
t 可达 10^12,C/C++ 需要 long long,Java/C# 需要 long,放进 int 会溢出。
JS、TS、Craykon 的数在 2^53(约 9×10^15)以内是精确的,所以 t 本身没问题。
但如果把整个计数器换成一个数来算,轮子格数的乘积可达 60^10(约 6×10^17),超过 2^53,低位就会出错。
逐个轮子用余数和商传下去,在任何语言里数值都停留在 t 附近。
Python 的 int(s / b) 也是在浮点除法后向 0 截断,是同一个陷阱。

Pythonmain.py · 14 行
n = int(input())b = list(map(int, input().split()))d = list(map(int, input().split()))t = int(input()) # 오른쪽 바퀴부터: 이 바퀴에 들어온 값 s를 칸(나머지)과 왼쪽으로 넘길 값(몫)으로 나눈다# Python의 %와 //는 내림 나눗셈이라 s가 음수여도 칸은 0 ~ b-1, 몫은 아래로 내린 값이다carry = tfor i in range(n - 1, -1, -1):    s = d[i] + carry    d[i] = s % b[i]    carry = s // b[i]print(*d)print(carry)  # 맨 왼쪽 바퀴에서도 넘친 값 = 계수기 전체가 넘어간 횟수
JavaScriptmain.js · 13 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const n = Number(lines[0]);const b = lines[1].trim().split(/\s+/).map(Number);const d = lines[2].trim().split(/\s+/).map(Number);let carry = Number(lines[3]); // 10^12는 2^53 안이라 Number로 정확하다 // JS의 %는 나누어지는 수의 부호를 따른다(-7 % 3 === -1) → b를 더해 다시 나눈다for (let i = n - 1; i >= 0; i--) {  const s = d[i] + carry;  d[i] = ((s % b[i]) + b[i]) % b[i];  carry = (s - d[i]) / b[i]; // s - 칸은 b로 딱 나누어떨어진다: 내림 몫}console.log(d.join(" ") + "\n" + carry);
TypeScriptmain.ts · 14 行
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").split("\n");const n: number = Number(lines[0]);const b: number[] = lines[1].trim().split(/\s+/).map(Number);const d: number[] = lines[2].trim().split(/\s+/).map(Number);let carry: number = Number(lines[3]); // 10^12는 2^53 안이라 number로 정확하다 // TS(JS)의 %는 나누어지는 수의 부호를 따른다(-7 % 3 === -1) → b를 더해 다시 나눈다for (let i = n - 1; i >= 0; i--) {  const s: number = d[i] + carry;  d[i] = ((s % b[i]) + b[i]) % b[i];  carry = (s - d[i]) / b[i]; // s - 칸은 b로 딱 나누어떨어진다: 내림 몫}console.log(d.join(" ") + "\n" + carry);
JavaMain.java · 24 行
import java.util.Scanner; public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int n = sc.nextInt();        int[] b = new int[n];        long[] d = new long[n];        for (int i = 0; i < n; i++) b[i] = sc.nextInt();        for (int i = 0; i < n; i++) d[i] = sc.nextLong();        long carry = sc.nextLong(); // 10^12: int로는 넘친다         // Java의 %와 /는 0 쪽으로 버린다(-7 % 3 == -1) → Math.floorMod·floorDiv는 내림으로 나눈다        for (int i = n - 1; i >= 0; i--) {            long s = d[i] + carry;            d[i] = Math.floorMod(s, (long) b[i]);            carry = Math.floorDiv(s, (long) b[i]);        }        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) out.append(i > 0 ? " " : "").append(d[i]);        out.append('\n').append(carry).append('\n');        System.out.print(out);    }}
C#Main.cs · 23 行
using System;using System.Linq; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine());        int[] b = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();        long[] d = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(long.Parse).ToArray();        long carry = long.Parse(Console.ReadLine().Trim()); // 10^12: int로는 넘친다         // C#의 %는 나누어지는 수의 부호를 따른다(-7 % 3 == -1) → b를 더해 다시 나눈다        for (int i = n - 1; i >= 0; i--)        {            long s = d[i] + carry;            d[i] = (s % b[i] + b[i]) % b[i];            carry = (s - d[i]) / b[i]; // s - 칸은 b로 딱 나누어떨어진다: 내림 몫        }        Console.WriteLine(string.Join(" ", d));        Console.WriteLine(carry);    }}
C++main.cpp · 21 行
#include <iostream>#include <vector> int main() {    int n;    std::cin >> n;    std::vector<long long> b(n), d(n);    for (auto& x : b) std::cin >> x;    for (auto& x : d) std::cin >> x;    long long carry; // t는 10^12까지: int로는 넘친다    std::cin >> carry;     // C++의 %는 0 쪽으로 버린 나눗셈의 나머지라 음수가 나올 수 있다 → b를 더해 다시 나눈다    for (int i = n - 1; i >= 0; i--) {        long long s = d[i] + carry;        d[i] = (s % b[i] + b[i]) % b[i];        carry = (s - d[i]) / b[i]; // s - 칸은 b로 딱 나누어떨어진다: 내림 몫    }    for (int i = 0; i < n; i++) std::cout << (i ? " " : "") << d[i];    std::cout << '\n' << carry << '\n';}
Cmain.c · 20 行
#include <stdio.h> int main(void) {    int n, b[10];    long long d[10], carry; /* t는 10^12까지: int로는 넘친다 */    scanf("%d", &n);    for (int i = 0; i < n; i++) scanf("%d", &b[i]);    for (int i = 0; i < n; i++) scanf("%lld", &d[i]);    scanf("%lld", &carry);     /* C의 %는 0 쪽으로 버린 나눗셈의 나머지라 음수가 나올 수 있다 → b를 더해 다시 나눈다 */    for (int i = n - 1; i >= 0; i--) {        long long s = d[i] + carry;        d[i] = (s % b[i] + b[i]) % b[i];        carry = (s - d[i]) / b[i]; /* s - 칸은 b로 딱 나누어떨어진다: 내림 몫 */    }    for (int i = 0; i < n; i++) printf(i ? " %lld" : "%lld", d[i]);    printf("\n%lld\n", carry);    return 0;}
Craykonmain.cray · 15 行
n = 控制台.读行().转数字();b = 控制台.读数字();d = 控制台.读数字();carry = 控制台.读行().转数字(); // 10^12는 2^53 안이라 정확하다 // 크레이콘의 %도 나누어지는 수의 부호를 따른다(-7 % 3 == -1) → b를 더해 다시 나눈다// 수학.몫은 0 쪽으로 버리니, 내림 몫은 (s - 칸) / b로 구한다(딱 나누어떨어진다)循环 (i = n - 1; i >= 0; i--){    s = d[i] + carry;    d[i] = (s % b[i] + b[i]) % b[i];    carry = (s - d[i]) / b[i];}控制台.输出(d.连接(" "));控制台.输出(carry);