IOI 1993
IOI 1993

Day 2, Task 1: Operations

BFS on the State Space This is a shortest-path problem in an unweighted graph where states are integers and transitions are the four operations. BFS finds the minimum number of steps. The key question is bounding the...

Updated May 21, 2026
Track IOI
Year 1993
Statement Rendered from TeX
TeXC++Rendered statement

Problem Statement

Rendered from the "Problem Statement" section in the LaTeX write-up.

Given a starting integer $s$ and a target integer $t$, find the minimum number of operations to transform $s$ into $t$. The allowed operations are:

  • Add 1: $x \to x + 1$

  • Subtract 1: $x \to x - 1$

  • Multiply by 2: $x \to 2x$

  • Integer divide by 2: $x \to \lfloor x/2 \rfloor$

  • Output the minimum number of steps and the operation sequence.

Editorial

The solution write-up is rendered from the LaTeX source, with equations kept live through MathJax.

Solution

BFS on the State Space

This is a shortest-path problem in an unweighted graph where states are integers and transitions are the four operations. BFS finds the minimum number of steps.

The key question is bounding the search range. For the operation set $\{+1, -1, \times 2, \div 2\}$:

Lemma.

An optimal path from $s$ to $t$ only visits values in $[\min(s,t) - 2,\; 2 \cdot \max(s,t) + 2]$.

This bound is loose but sufficient; the actual BFS terminates quickly because doubling shrinks the gap fast.

Alternative: Working Backwards

A cleaner approach works backwards from the target:

  • If $t > s$ and $t$ is even, halve $t$ (inverse of $\times 2$).

  • If $t > s$ and $t$ is odd, add 1 to $t$ first, then halve (inverse of $-1$ then $\times 2$).

  • If $t \le s$, the answer is $s - t$ steps of $-1$.

  • This greedy backward approach gives $O(\log t)$ steps. However, it does not always produce the optimal sequence for the full 4-operation set (the BFS approach is exact).

Complexity Analysis

  • BFS: $O(|V| + |E|)$ where $|V|$ is bounded by the search range, typically $O(\max(s,t))$.

  • Backward greedy: $O(\log t)$ time.

Implementation: BFS

#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int start, target;
    cin >> start >> target;

    if (start == target) {
        cout << 0 << endl;
        return 0;
    }

    unordered_map<int, int> dist;
    unordered_map<int, int> parent;
    unordered_map<int, string> op;

    queue<int> q;
    q.push(start);
    dist[start] = 0;

    int lo = min(start, target) - 2;
    int hi = max(start, target) * 2 + 2;

    while (!q.empty()) {
        int cur = q.front(); q.pop();

        struct Move { int nxt; string name; };
        Move moves[] = {
            {cur + 1, "+1"},
            {cur - 1, "-1"},
            {cur * 2, "*2"},
            {cur / 2, "/2"}
        };

        for (auto& [nxt, name] : moves) {
            if (nxt < lo || nxt > hi) continue;
            if (dist.count(nxt)) continue;

            dist[nxt] = dist[cur] + 1;
            parent[nxt] = cur;
            op[nxt] = name;

            if (nxt == target) {
                cout << dist[nxt] << endl;
                vector<string> path;
                int x = target;
                while (x != start) {
                    path.push_back(op[x]);
                    x = parent[x];
                }
                reverse(path.begin(), path.end());
                for (auto& s : path)
                    cout << s << " ";
                cout << endl;
                return 0;
            }
            q.push(nxt);
        }
    }

    cout << -1 << endl; // unreachable (should not happen)
    return 0;
}

Implementation: Backward Greedy

This approach works backwards from the target and produces the forward operation sequence. It is optimal for the operation set $\{+1, -1, \times 2\}$ but may not be optimal when $\div 2$ is also available.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    int start, target;
    cin >> start >> target;

    vector<string> revOps; // operations in reverse order
    int cur = target;

    while (cur > start) {
        if (cur % 2 == 0) {
            cur /= 2;
            revOps.push_back("*2");
        } else {
            cur++;
            revOps.push_back("-1");
        }
    }

    // Now cur <= start; need (start - cur) subtract-1 operations
    for (int i = 0; i < start - cur; i++)
        revOps.push_back("-1");

    reverse(revOps.begin(), revOps.end());

    cout << revOps.size() << endl;
    for (auto& s : revOps)
        cout << s << " ";
    cout << endl;

    return 0;
}

Notes

  • The BFS approach guarantees optimality for any operation set.

  • The backward greedy approach is simpler and runs in $O(\log t)$ time, but is only provably optimal for $\{+1, -1, \times 2\}$.

  • For the original IOI constraints, BFS within a bounded range is sufficient.

Code

C++ solution used for this page.

C++

Clean code view with a raw-file link when you want the original source.

Raw file
// IOI 1993 - Day 2, Task 1: Operations
// BFS to find minimum operations to transform start into target.
// Operations: +1, -1, *2, /2 (integer division).
#include <bits/stdc++.h>
using namespace std;

int main() {
    int start, target;
    scanf("%d%d", &start, &target);

    if (start == target) {
        printf("0\n");
        return 0;
    }

    unordered_map<int, int> dist;
    unordered_map<int, int> parent;
    unordered_map<int, string> op;

    queue<int> q;
    q.push(start);
    dist[start] = 0;

    // Search bounds
    int lo = min(0, min(start, target) - 1);
    int hi = max(start, target) * 2 + 2;

    while (!q.empty()) {
        int cur = q.front(); q.pop();

        int nexts[] = {cur + 1, cur - 1, cur * 2, cur / 2};
        const char* ops[] = {"+1", "-1", "*2", "/2"};
        int cnt = (cur != 0) ? 4 : 3;

        for (int i = 0; i < cnt; i++) {
            int nxt = nexts[i];
            if (nxt < lo || nxt > hi) continue;
            if (dist.count(nxt)) continue;

            dist[nxt] = dist[cur] + 1;
            parent[nxt] = cur;
            op[nxt] = ops[i];

            if (nxt == target) {
                printf("%d\n", dist[nxt]);
                vector<string> path;
                int x = target;
                while (x != start) {
                    path.push_back(op[x]);
                    x = parent[x];
                }
                reverse(path.begin(), path.end());
                for (auto& s : path) printf("%s ", s.c_str());
                printf("\n");
                return 0;
            }
            q.push(nxt);
        }
    }

    printf("-1\n");
    return 0;
}

Source Files and Assets

Raw files are still available here when you want the original TeX, C++, or statement assets.

Show raw files