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...
Source-first archive entry
This page is built from the copied files in competitive_programming/ioi/1993/operations. Edit
competitive_programming/ioi/1993/operations/solution.tex to update the written solution and
competitive_programming/ioi/1993/operations/solution.cpp to update the implementation.
The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.
Problem Statement
Rendered from the "Problem Statement" section inside solution.tex because this entry has no separate statement file.
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
Rendered from the copied solution.tex file. The original TeX source remains
available below.
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
Exact copied C++ implementation from solution.cpp.
// 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
Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage[margin=2.5cm]{geometry}
\usepackage{xcolor}
\newtheorem{theorem}{Theorem}
\newtheorem{lemma}[theorem]{Lemma}
\lstset{
language=C++,
basicstyle=\ttfamily\small,
keywordstyle=\color{blue}\bfseries,
commentstyle=\color{green!50!black},
stringstyle=\color{red!70!black},
numbers=left,
numberstyle=\tiny\color{gray},
breaklines=true,
frame=single,
tabsize=4,
showstringspaces=false
}
\title{IOI 1993 -- Day 2, Task 1: Operations}
\author{Solution}
\date{}
\begin{document}
\maketitle
\section{Problem Statement}
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:
\begin{itemize}
\item Add 1: $x \to x + 1$
\item Subtract 1: $x \to x - 1$
\item Multiply by 2: $x \to 2x$
\item Integer divide by 2: $x \to \lfloor x/2 \rfloor$
\end{itemize}
Output the minimum number of steps and the operation sequence.
\section{Solution}
\subsection{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\}$:
\begin{lemma}
An optimal path from $s$ to $t$ only visits values in
$[\min(s,t) - 2,\; 2 \cdot \max(s,t) + 2]$.
\end{lemma}
This bound is loose but sufficient; the actual BFS terminates quickly because
doubling shrinks the gap fast.
\subsection{Alternative: Working Backwards}
A cleaner approach works backwards from the target:
\begin{itemize}
\item If $t > s$ and $t$ is even, halve $t$ (inverse of $\times 2$).
\item If $t > s$ and $t$ is odd, add 1 to $t$ first, then halve
(inverse of $-1$ then $\times 2$).
\item If $t \le s$, the answer is $s - t$ steps of $-1$.
\end{itemize}
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).
\section{Complexity Analysis}
\begin{itemize}
\item \textbf{BFS:} $O(|V| + |E|)$ where $|V|$ is bounded by the search
range, typically $O(\max(s,t))$.
\item \textbf{Backward greedy:} $O(\log t)$ time.
\end{itemize}
\section{Implementation: BFS}
\begin{lstlisting}
#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;
}
\end{lstlisting}
\section{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.
\begin{lstlisting}
#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;
}
\end{lstlisting}
\section{Notes}
\begin{itemize}
\item The BFS approach guarantees optimality for any operation set.
\item The backward greedy approach is simpler and runs in $O(\log t)$
time, but is only provably optimal for $\{+1, -1, \times 2\}$.
\item For the original IOI constraints, BFS within a bounded range is
sufficient.
\end{itemize}
\end{document}
// 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;
}