{ }codelper
BFSqueueP0210

The order of moves to measure with two jugs

There are two jugs with no markings.
Jug A holds up to A liters, and jug B holds up to B liters. At first both jugs are empty, and each move is one of the following six actions.
Each action has a number. - 1: fill jug A from the tap.
2: fill jug B. - 3: pour out all of jug A.
4: pour out all of jug B. - 5: pour jug A into jug B.
Pour until A is empty or B is full; no water spills or disappears. - 6: pour jug B into jug A in the same way. Find the shortest sequence of actions that leaves exactly t liters in either one of the jugs. If there are several shortest sequences, compare their action numbers from the front and choose the one with the smaller number at the first differing position. Write the answer as the number of actions k first, followed by the k action numbers in order. If one jug already holds t liters at the start (t = 0), print just 0.
If it can never be done, print -1.

Input
First line the number of questions q (1 ≤ q ≤ 20).
Then q lines of A, B, t (1 ≤ A, B ≤ 90, 0 ≤ t ≤ 90)
Output
One line per question: "k action1 … actionk" (separated by single spaces), or -1
Example 1 · stdin
4
3 5 4
2 4 3
7 7 0
1 4 2
stdout
6 2 6 3 6 2 6
-1
0
4 1 5 1 5
main.py
Input (stdin)

When you run, these lines are fed to input()/stdin in order (the program doesn't stop to ask).

$ Ready

Open this on a computer to write and run code. On mobile you can read the problem and the solutions.

In other languagesShow solutions by language · 8 languages

If you treat the amounts in the two jugs (a, b) as one cell, one action is one step from cell to cell.
The shortest sequence is found with breadth-first search (BFS).
Taking from the front of a queue and adding new cells at the back brings out the 0-step cells, then the 1-step cells, then the 2-step cells, so the first time the goal cell comes out is the shortest.
For each cell record "the cell it was first reached from" and "the action taken then"; from the goal cell, follow those back to (0, 0) to get the sequence.
If you try actions from number 1 upward and record a cell only the first time it is reached, cells with the same step count enter the queue in order of the earliest sequence, so the sequence with the smaller numbers is chosen automatically.
There are at most 91 × 91 cells, so turn (a, b) into one number a * (B + 1) + b and record everything in one list.
Queues differ by language: Python collections.deque with popleft(), Java ArrayDeque with add/poll, C# Queue<T> with Enqueue/Dequeue, C++ std::queue with push/front/pop.
C has no queue, so build one from an array with head and tail indexes, and in JS/TS shift() can be slow because it removes from the front, so keep a head index into the array and move it.
Craykon also adds with Add and moves a head index, recording the previous cell and the action in NewList.
"Not found" is returned as Python None, JS/TS/Java/C# null, C++ std::optional, Craykon null.
Six traps: (1) Taking from the back (Python pop()) makes a stack, i.e. depth-first search, and the first path found is not the shortest.
Java ArrayDeque's push/pop both use the front, so they also make a stack.
(2) The amount to pour is min(a, B − b).
Throwing away the rest when the receiving jug overflows (setting the pouring jug to 0) changes which amounts you can make.
(3) Checking the goal only on newly added cells misses the start cell (0, 0) and answers one action instead of 0 for t = 0.
(4) The goal is "either jug", so B alone holding t also counts.
(5) The actions collected by following previous cells come out backwards, from the end to the start.
Reverse them before printing (Python [::-1], JS/TS reverse(), Java Collections.reverse, C# List.Reverse(), C++ std::reverse, Craykon Reverse()).
(6) If you do not try actions in number order (say, B's actions first), you get a different sequence when the A side and the B side have equally short paths (as in 1 4 2).

Pythonmain.py · 36 lines
from collections import deque  def shortest_plan(A, B, t):    """빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 None."""    w = B + 1  # 상태 (a, b)를 수 하나 a * w + b로 적는다    prev = [-1] * ((A + 1) * w)  # 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)    move = [0] * ((A + 1) * w)  # 그때 한 동작 번호    prev[0] = 0    queue = deque([0])    while queue:        s = queue.popleft()        a, b = divmod(s, w)        if a == t or b == t:  # 꺼낸 차례가 곧 가깝고 앞선 차례다            plan = []            while s != 0:  # 앞 상태를 따라가면 끝에서 처음 쪽으로 거꾸로 나온다                plan.append(move[s])                s = prev[s]            return plan[::-1]        ab = min(a, B - b)  # A에서 B로 부을 수 있는 양        ba = min(b, A - a)  # B에서 A로 부을 수 있는 양        nexts = [(A, b), (a, B), (0, b), (a, 0), (a - ab, b + ab), (a + ba, b - ba)]        for k, (na, nb) in enumerate(nexts, start=1):  # 동작 번호가 작은 것부터            n = na * w + nb            if prev[n] < 0:  # 처음 닿을 때만 적는다(넣을 때 표시)                prev[n] = s                move[n] = k                queue.append(n)    return None  q = int(input())for _ in range(q):    A, B, t = map(int, input().split())    plan = shortest_plan(A, B, t)    print(-1 if plan is None else " ".join(map(str, [len(plan)] + plan)))
JavaScriptmain.js · 41 lines
const lines = require("fs").readFileSync(0, "utf8").trim().split("\n"); // 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 nullfunction shortestPlan(A, B, t) {  const w = B + 1; // 상태 (a, b)를 수 하나 a * w + b로 적는다  const prev = new Array((A + 1) * w).fill(-1); // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)  const move = new Array((A + 1) * w).fill(0); // 그때 한 동작 번호  prev[0] = 0;  const queue = [0];  for (let head = 0; head < queue.length; head++) { // shift() 대신 머리 번호를 옮긴다    let s = queue[head];    const a = Math.floor(s / w);    const b = s % w;    if (a === t || b === t) { // 꺼낸 차례가 곧 가깝고 앞선 차례다      const plan = [];      for (; s !== 0; s = prev[s]) plan.push(move[s]); // 끝에서 처음 쪽으로 거꾸로 나온다      return plan.reverse();    }    const ab = Math.min(a, B - b); // A에서 B로 부을 수 있는 양    const ba = Math.min(b, A - a); // B에서 A로 부을 수 있는 양    const nexts = [[A, b], [a, B], [0, b], [a, 0], [a - ab, b + ab], [a + ba, b - ba]];    nexts.forEach(([na, nb], k) => { // 동작 번호(k + 1)가 작은 것부터      const n = na * w + nb;      if (prev[n] < 0) { // 처음 닿을 때만 적는다(넣을 때 표시)        prev[n] = s;        move[n] = k + 1;        queue.push(n);      }    });  }  return null;} const q = Number(lines[0]);const out = [];for (let i = 1; i <= q; i++) {  const [A, B, t] = lines[i].trim().split(" ").map(Number);  const plan = shortestPlan(A, B, t);  out.push(plan === null ? "-1" : [plan.length, ...plan].join(" "));}console.log(out.join("\n"));
TypeScriptmain.ts · 42 lines
import { readFileSync } from "node:fs";const lines: string[] = readFileSync(0, "utf8").trim().split("\n"); // 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 nullfunction shortestPlan(A: number, B: number, t: number): number[] | null {  const w = B + 1; // 상태 (a, b)를 수 하나 a * w + b로 적는다  const prev: number[] = new Array((A + 1) * w).fill(-1); // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)  const move: number[] = new Array((A + 1) * w).fill(0); // 그때 한 동작 번호  prev[0] = 0;  const queue: number[] = [0];  for (let head = 0; head < queue.length; head++) { // shift() 대신 머리 번호를 옮긴다    let s = queue[head];    const a = Math.floor(s / w);    const b = s % w;    if (a === t || b === t) { // 꺼낸 차례가 곧 가깝고 앞선 차례다      const plan: number[] = [];      for (; s !== 0; s = prev[s]) plan.push(move[s]); // 끝에서 처음 쪽으로 거꾸로 나온다      return plan.reverse();    }    const ab = Math.min(a, B - b); // A에서 B로 부을 수 있는 양    const ba = Math.min(b, A - a); // B에서 A로 부을 수 있는 양    const nexts: [number, number][] = [[A, b], [a, B], [0, b], [a, 0], [a - ab, b + ab], [a + ba, b - ba]];    nexts.forEach(([na, nb], k) => { // 동작 번호(k + 1)가 작은 것부터      const n = na * w + nb;      if (prev[n] < 0) { // 처음 닿을 때만 적는다(넣을 때 표시)        prev[n] = s;        move[n] = k + 1;        queue.push(n);      }    });  }  return null;} const q = Number(lines[0]);const out: string[] = [];for (let i = 1; i <= q; i++) {  const [A, B, t] = lines[i].trim().split(" ").map(Number);  const plan = shortestPlan(A, B, t);  out.push(plan === null ? "-1" : [plan.length, ...plan].join(" "));}console.log(out.join("\n"));
JavaMain.java · 66 lines
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.List;import java.util.StringTokenizer; public class Main {    // 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 null    // 상태 (a, b)는 수 하나 a * (B + 1) + b로 적는다    static List<Integer> shortestPlan(int A, int B, int t) {        int w = B + 1;        int[] prev = new int[(A + 1) * w]; // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)        int[] move = new int[(A + 1) * w]; // 그때 한 동작 번호        Arrays.fill(prev, -1);        prev[0] = 0;        ArrayDeque<Integer> queue = new ArrayDeque<>();        queue.add(0);        while (!queue.isEmpty()) {            int s = queue.poll(); // 앞에서 꺼낸다(add는 뒤에 넣는다)            int a = s / w, b = s % w;            if (a == t || b == t) { // 꺼낸 차례가 곧 가깝고 앞선 차례다                List<Integer> plan = new ArrayList<>();                for (; s != 0; s = prev[s]) plan.add(move[s]); // 끝에서 처음 쪽으로 거꾸로                Collections.reverse(plan);                return plan;            }            int ab = Math.min(a, B - b); // A에서 B로 부을 수 있는 양            int ba = Math.min(b, A - a); // B에서 A로 부을 수 있는 양            int[][] nexts = {{A, b}, {a, B}, {0, b}, {a, 0}, {a - ab, b + ab}, {a + ba, b - ba}};            for (int k = 0; k < 6; k++) { // 동작 번호(k + 1)가 작은 것부터                int n = nexts[k][0] * w + nexts[k][1];                if (prev[n] < 0) { // 처음 닿을 때만 적는다(넣을 때 표시)                    prev[n] = s;                    move[n] = k + 1;                    queue.add(n);                }            }        }        return null;    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int q = Integer.parseInt(in.readLine().trim());        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            StringTokenizer st = new StringTokenizer(in.readLine());            int A = Integer.parseInt(st.nextToken());            int B = Integer.parseInt(st.nextToken());            int t = Integer.parseInt(st.nextToken());            List<Integer> plan = shortestPlan(A, B, t);            if (plan == null) {                out.append(-1).append('\n');                continue;            }            out.append(plan.size());            for (int k : plan) out.append(' ').append(k);            out.append('\n');        }        System.out.print(out);    }}
C#Main.cs · 58 lines
using System;using System.Collections.Generic;using System.Linq;using System.Text; class Program{    // 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 null    // 상태 (a, b)는 수 하나 a * (B + 1) + b로 적는다    static List<int>? ShortestPlan(int A, int B, int t)    {        int w = B + 1;        var prev = Enumerable.Repeat(-1, (A + 1) * w).ToArray(); // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)        var move = new int[(A + 1) * w]; // 그때 한 동작 번호        prev[0] = 0;        var queue = new Queue<int>();        queue.Enqueue(0);        while (queue.Count > 0)        {            int s = queue.Dequeue();            int a = s / w, b = s % w;            if (a == t || b == t) // 꺼낸 차례가 곧 가깝고 앞선 차례다            {                var plan = new List<int>();                for (; s != 0; s = prev[s]) plan.Add(move[s]); // 끝에서 처음 쪽으로 거꾸로                plan.Reverse();                return plan;            }            int ab = Math.Min(a, B - b); // A에서 B로 부을 수 있는 양            int ba = Math.Min(b, A - a); // B에서 A로 부을 수 있는 양            var nexts = new[] { (A, b), (a, B), (0, b), (a, 0), (a - ab, b + ab), (a + ba, b - ba) };            for (int k = 0; k < 6; k++) // 동작 번호(k + 1)가 작은 것부터            {                int n = nexts[k].Item1 * w + nexts[k].Item2;                if (prev[n] < 0) // 처음 닿을 때만 적는다(넣을 때 표시)                {                    prev[n] = s;                    move[n] = k + 1;                    queue.Enqueue(n);                }            }        }        return null;    }     static void Main()    {        int q = int.Parse(Console.ReadLine()!.Trim());        var output = new StringBuilder();        for (int i = 0; i < q; i++)        {            int[] p = Console.ReadLine()!.Trim().Split(' ').Select(int.Parse).ToArray();            var plan = ShortestPlan(p[0], p[1], p[2]);            output.Append(plan == null ? "-1" : string.Join(" ", plan.Prepend(plan.Count))).Append('\n');        }        Console.Write(output);    }}
C++main.cpp · 56 lines
#include <algorithm>#include <iostream>#include <optional>#include <queue>#include <vector> // 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 빈 optional// 상태 (a, b)는 수 하나 a * (B + 1) + b로 적는다std::optional<std::vector<int>> shortestPlan(int A, int B, int t) {    int w = B + 1;    std::vector<int> prev((A + 1) * w, -1); // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)    std::vector<int> move((A + 1) * w, 0); // 그때 한 동작 번호    std::queue<int> queue;    prev[0] = 0;    queue.push(0);    while (!queue.empty()) {        int s = queue.front(); // front()로 읽고 pop()으로 뺀다        queue.pop();        int a = s / w, b = s % w;        if (a == t || b == t) { // 꺼낸 차례가 곧 가깝고 앞선 차례다            std::vector<int> plan;            for (; s != 0; s = prev[s]) plan.push_back(move[s]); // 끝에서 처음 쪽으로 거꾸로            std::reverse(plan.begin(), plan.end());            return plan;        }        int ab = std::min(a, B - b); // A에서 B로 부을 수 있는 양        int ba = std::min(b, A - a); // B에서 A로 부을 수 있는 양        int nexts[6][2] = {{A, b}, {a, B}, {0, b}, {a, 0}, {a - ab, b + ab}, {a + ba, b - ba}};        for (int k = 0; k < 6; k++) { // 동작 번호(k + 1)가 작은 것부터            int n = nexts[k][0] * w + nexts[k][1];            if (prev[n] < 0) { // 처음 닿을 때만 적는다(넣을 때 표시)                prev[n] = s;                move[n] = k + 1;                queue.push(n);            }        }    }    return std::nullopt;} int main() {    int q;    std::cin >> q;    while (q--) {        int A, B, t;        std::cin >> A >> B >> t;        auto plan = shortestPlan(A, B, t);        if (!plan) {            std::cout << -1 << '\n';            continue;        }        std::cout << plan->size();        for (int k : *plan) std::cout << ' ' << k;        std::cout << '\n';    }}
Cmain.c · 61 lines
#include <stdio.h> #define MAXS (91 * 91) static int prev[MAXS]; /* 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄) */static int move[MAXS]; /* 그때 한 동작 번호 */static int queue[MAXS]; /* 상태마다 한 번만 들어가니 이 크기면 된다 */static int plan[MAXS]; static int min(int x, int y) { return x < y ? x : y; } /* 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호(가장 앞선 것)를 plan에 채우고 개수를 돌려준다. 안 되면 -1   상태 (a, b)는 수 하나 a * (B + 1) + b로 적는다 */static int shortest_plan(int A, int B, int t) {    int w = B + 1;    for (int s = 0; s < (A + 1) * w; s++) prev[s] = -1;    int head = 0, tail = 0;    prev[0] = 0;    queue[tail++] = 0;    while (head < tail) {        int s = queue[head++];        int a = s / w, b = s % w;        if (a == t || b == t) { /* 꺼낸 차례가 곧 가깝고 앞선 차례다 */            int len = 0;            for (; s != 0; s = prev[s]) plan[len++] = move[s]; /* 끝에서 처음 쪽으로 거꾸로 */            for (int i = 0, j = len - 1; i < j; i++, j--) {                int tmp = plan[i];                plan[i] = plan[j];                plan[j] = tmp;            }            return len;        }        int ab = min(a, B - b); /* A에서 B로 부을 수 있는 양 */        int ba = min(b, A - a); /* B에서 A로 부을 수 있는 양 */        int na[6] = {A, a, 0, a, a - ab, a + ba};        int nb[6] = {b, B, b, 0, b + ab, b - ba};        for (int k = 0; k < 6; k++) { /* 동작 번호(k + 1)가 작은 것부터 */            int n = na[k] * w + nb[k];            if (prev[n] < 0) { /* 처음 닿을 때만 적는다(넣을 때 표시) */                prev[n] = s;                move[n] = k + 1;                queue[tail++] = n;            }        }    }    return -1;} int main(void) {    int q;    scanf("%d", &q);    while (q--) {        int A, B, t;        scanf("%d %d %d", &A, &B, &t);        int len = shortest_plan(A, B, t);        printf("%d", len);        for (int i = 0; i < len; i++) printf(" %d", plan[i]);        printf("\n");    }    return 0;}
Craykonmain.cray · 59 lines
// 빈 두 물통에서 한쪽에 딱 t리터를 담는 가장 짧은 동작 번호 목록(가장 앞선 것). 안 되면 빈값// 상태 (a, b)는 수 하나 a * (B + 1) + b로 적는다가장짧은계획(A, B, t){    w = B + 1;    prev = NewList((A + 1) * w, -1); // 그 상태로 처음 들어온 앞 상태(-1이면 아직 못 가 봄)    move = NewList((A + 1) * w, 0); // 그때 한 동작 번호    prev[0] = 0;    queue = [0];    head = 0; // 목록 앞에서 빼지 않고 머리 번호를 옮긴다    while (head < queue.Count)    {        s = queue[head];        head += 1;        a = Mathf.Quot(s, w);        b = s % w;        if (a == t || b == t)        {            plan = [];            while (s != 0)            {                plan.Add(move[s]);                s = prev[s];            }            plan.Reverse();            return plan;        }        ab = Mathf.Min(a, B - b); // A에서 B로 부을 수 있는 양        ba = Mathf.Min(b, A - a); // B에서 A로 부을 수 있는 양        nexts = [A * w + b, a * w + B, b, a * w, (a - ab) * w + b + ab, (a + ba) * w + b - ba];        for (k = 0; k < 6; k++)        {            n = nexts[k];            if (prev[n] < 0)            {                prev[n] = s;                move[n] = k + 1;                queue.Add(n);            }        }    }    return null;} q = Console.ReadLine().Trim().ToNumber();for (i = 0; i < q; i++){    p = Console.ReadNumbers();    plan = 가장짧은계획(p[0], p[1], p[2]);    if (plan == null)    {        Console.WriteLine(-1);    }    else    {        plan.Insert(0, plan.Count);        Console.WriteLine(plan.Join(" "));    }}