IOI 1997
IOI 1997

The Buses

Problem Statement A person observes bus arrivals at a stop and records arrival times during an observation period. From these times, determine the bus routes (schedules). Each bus route is characterized by: A start ti...

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

Problem Statement

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

A person observes bus arrivals at a stop and records arrival times during an observation period. From these times, determine the bus routes (schedules).

Each bus route is characterized by:

  • A start time $s$ (first arrival).

  • An interval $d > 0$ (time between consecutive buses).

  • A count $c \ge 2$ (number of buses on this route).

  • Every recorded arrival must belong to exactly one route. Find a set of routes that explains all arrivals using the minimum number of routes.

    Constraints: Number of arrivals $n \le 300$, arrival times $\le 59$.

Editorial

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

Solution Approach

Backtracking with Pruning

The small time range (0--59) makes backtracking feasible:

  1. Maintain a frequency array $\mathtt{cnt}[0..59]$ of unassigned arrival times.

  2. Find the smallest unassigned time $t_{\min}$. This time must be the start of some route, so we try all routes beginning at $t_{\min}$.

  3. For each interval $d \in [1, 59]$, check the maximum number of evenly spaced arrivals $t_{\min}, t_{\min}+d, t_{\min}+2d, \ldots$ that are all available. For each valid count $c \ge 2$ (tried largest first to cover more arrivals per route), subtract the route from $\mathtt{cnt}$ and recurse.

  4. Prune: if the current number of routes already equals the best found, stop.

Correctness

The key invariant is that the smallest unassigned time must start some route. By trying all possible intervals and counts for that starting time, we enumerate all valid decompositions. The pruning condition ensures we find a minimum-route solution.

C++ Solution

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

int n;
int cnt[60]; // available count for each arrival time
int bestRoutes;

struct Route {
    int start, interval, count;
};
vector<Route> bestSolution, currentSolution;

bool canUse(int s, int d, int c) {
    for (int i = 0; i < c; i++) {
        int t = s + i * d;
        if (t > 59 || cnt[t] <= 0) return false;
    }
    return true;
}

void applyRoute(int s, int d, int c, int delta) {
    for (int i = 0; i < c; i++)
        cnt[s + i * d] += delta;
}

int remaining() {
    int r = 0;
    for (int i = 0; i < 60; i++) r += cnt[i];
    return r;
}

void solve() {
    if (remaining() == 0) {
        if ((int)currentSolution.size() < bestRoutes) {
            bestRoutes = (int)currentSolution.size();
            bestSolution = currentSolution;
        }
        return;
    }

    // Prune: cannot improve on current best
    if ((int)currentSolution.size() + 1 >= bestRoutes) return;

    // Find first unassigned time (must be the start of some route)
    int first = -1;
    for (int i = 0; i < 60; i++)
        if (cnt[i] > 0) { first = i; break; }

    // Try all routes starting at 'first'
    for (int d = 1; d <= 59; d++) {
        // Count maximum consecutive multiples present
        int maxc = 0;
        for (int t = first; t <= 59; t += d) {
            if (cnt[t] > 0) maxc++;
            else break;
        }
        if (maxc < 2) continue;

        // Try from largest count downward (greedy: cover more with fewer routes)
        for (int c = maxc; c >= 2; c--) {
            if (canUse(first, d, c)) {
                applyRoute(first, d, c, -1);
                currentSolution.push_back({first, d, c});
                solve();
                currentSolution.pop_back();
                applyRoute(first, d, c, +1);
            }
        }
    }
}

int main() {
    scanf("%d", &n);
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; i++) {
        int t;
        scanf("%d", &t);
        cnt[t]++;
    }

    bestRoutes = n; // upper bound
    solve();

    printf("%d\n", bestRoutes);
    for (auto& r : bestSolution)
        printf("Start: %d, Interval: %d, Count: %d\n",
               r.start, r.interval, r.count);

    return 0;
}

Complexity Analysis

  • Time complexity: Exponential in the worst case, but heavily pruned. At each recursion level, we fix the smallest unassigned time and try $O(59)$ intervals, each with $O(59)$ possible counts. The pruning bound on the number of routes limits the recursion depth. In practice, for $n \le 300$ and times $\le 59$, the search terminates quickly.

  • Space complexity: $O(n)$ for the route stack and the frequency array.

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 1997 - The Buses
// Backtracking: find minimum number of bus routes explaining all arrivals
// Each route: start time, interval, count >= 2
// Time: exponential with pruning, Space: O(n)
#include <bits/stdc++.h>
using namespace std;

int n;
int cnt[60]; // count of each arrival time available
int bestRoutes;

struct Route {
    int start, interval, count;
};
vector<Route> bestSolution, currentSolution;

// Check if a route (start, interval, count) is valid with current counts
bool canUse(int s, int d, int c) {
    for (int i = 0; i < c; i++) {
        int t = s + i * d;
        if (t > 59 || cnt[t] <= 0) return false;
    }
    return true;
}

void useRoute(int s, int d, int c, int delta) {
    for (int i = 0; i < c; i++)
        cnt[s + i * d] += delta;
}

int remaining() {
    int r = 0;
    for (int i = 0; i < 60; i++) r += cnt[i];
    return r;
}

void solve() {
    if (remaining() == 0) {
        if ((int)currentSolution.size() < bestRoutes) {
            bestRoutes = (int)currentSolution.size();
            bestSolution = currentSolution;
        }
        return;
    }

    if ((int)currentSolution.size() + 1 >= bestRoutes) return;

    // Find first unassigned time
    int first = -1;
    for (int i = 0; i < 60; i++)
        if (cnt[i] > 0) { first = i; break; }

    // Try all routes starting at 'first'
    for (int d = 1; d <= 59; d++) {
        // Find max count for this interval
        int maxc = 0;
        for (int t = first; t <= 59; t += d) {
            if (cnt[t] > 0) maxc++;
            else break;
        }
        if (maxc < 2) continue;

        // Try from largest count downward (greedy: cover more with one route)
        for (int c = maxc; c >= 2; c--) {
            if (canUse(first, d, c)) {
                useRoute(first, d, c, -1);
                currentSolution.push_back({first, d, c});
                solve();
                currentSolution.pop_back();
                useRoute(first, d, c, +1);
            }
        }
    }
}

int main() {
    scanf("%d", &n);
    memset(cnt, 0, sizeof(cnt));
    for (int i = 0; i < n; i++) {
        int t;
        scanf("%d", &t);
        cnt[t]++;
    }

    bestRoutes = n; // worst case
    solve();

    printf("%d\n", bestRoutes);
    for (auto& r : bestSolution)
        printf("%d %d %d\n", r.start, r.interval, r.count);

    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