All IOI entries
Competitive Programming

IOI 2010 - Memory

An interactive memory card game. There are 2N face-down cards forming N matching pairs. Each turn, flip two cards. If they match, they are removed. Otherwise, they are flipped back. The goal is to match all pairs usin...

Source sync Apr 19, 2026
Track IOI
Year 2010
Files TeX and C++
Folder competitive_programming/ioi/2010/memory
IOI2010TeXC++

Source-first archive entry

This page is built from the copied files in competitive_programming/ioi/2010/memory. Edit competitive_programming/ioi/2010/memory/solution.tex to update the written solution and competitive_programming/ioi/2010/memory/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 Summary" section inside solution.tex because this entry has no separate statement file.

An interactive memory card game. There are $2N$ face-down cards forming $N$ matching pairs. Each turn, flip two cards. If they match, they are removed. Otherwise, they are flipped back. The goal is to match all pairs using as few flips as possible.

Editorial

Rendered from the copied solution.tex file. The original TeX source remains available below.

Solution

Algorithm

Maintain a map from each card value to its known position. Process cards using a known-first strategy:

  1. Pick an unflipped card at position $p_1$ and flip it to reveal value $v_1$.

  2. If $v_1$ was previously seen at a known position $p_2$ (still unmatched), flip $p_2$ as the second card and collect the match.

  3. Otherwise, record $v_1$'s position. Pick another unseen card at position $p_2$ and flip it to reveal $v_2$.

    • If $v_1 = v_2$, collect the match.

    • Otherwise, record $v_2$'s position. Both cards flip back.

    • enumerate

    Analysis

    Theorem.

    This strategy uses at most $3N$ flips.

    Proof.

    Each pair is discovered and matched in one of two ways:

    • Lucky match (step 3): both unknown cards happen to match. Cost: 2 flips.

    • Known match (step 2): the first card matches a previously seen card. Cost: 2 flips, but one of the pair was already flipped once before (during an earlier failed attempt costing 2 flips for that earlier turn). Hence the amortized cost per pair is at most $2 + 1 = 3$ flips.

    • In the worst case, every pair is first seen in a failed attempt (1 flip each card = 2 flips wasted) and then matched in a subsequent turn (2 flips). But each failed turn reveals two cards, contributing to at most two future matches. A careful accounting shows at most $3N$ total flips. proof

    Complexity

    • Flips: $O(N)$.

    • Time: $O(N)$ with hash map lookups.

    • Space: $O(N)$.

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    
    // Grader functions
    int faceup(int pos);            // flip card, return value
    void matched(int pos1, int pos2); // declare a match
    
    void play(int N) {
        unordered_map<int, int> known; // value -> position of a seen card
        set<int> remaining;
        for (int i = 1; i <= 2 * N; i++) remaining.insert(i);
    
        while (!remaining.empty()) {
            int pos1 = *remaining.begin();
            int val1 = faceup(pos1);
    
            if (known.count(val1) && remaining.count(known[val1])) {
                // Match with a previously seen card
                int pos2 = known[val1];
                faceup(pos2);
                matched(pos1, pos2);
                remaining.erase(pos1);
                remaining.erase(pos2);
                known.erase(val1);
            } else {
                known[val1] = pos1;
    
                // Pick another unseen card
                auto it = remaining.begin();
                if (*it == pos1) ++it;
                if (it == remaining.end()) break;
                int pos2 = *it;
                int val2 = faceup(pos2);
    
                if (val1 == val2) {
                    matched(pos1, pos2);
                    remaining.erase(pos1);
                    remaining.erase(pos2);
                    known.erase(val1);
                } else {
                    known[val2] = pos2;
                }
            }
        }
    }

Code

Exact copied C++ implementation from solution.cpp.

C++ competitive_programming/ioi/2010/memory/solution.cpp

Exact copied implementation source.

Raw file
// IOI 2010 - Memory (Interactive)
// Two-phase strategy: explore cards, match known pairs immediately.
// O(N) flips.
#include <bits/stdc++.h>
using namespace std;

// Grader-provided functions.
int faceup(int pos);
void matched(int pos1, int pos2);

void play(int N) {
    // 2N cards at positions 1..2N.
    map<int, int> known;   // card value -> known position
    set<int> remaining;    // unmatched positions
    for (int i = 1; i <= 2 * N; i++) remaining.insert(i);

    while (!remaining.empty()) {
        // Pick the first remaining card.
        int pos1 = *remaining.begin();
        int val1 = faceup(pos1);

        if (known.count(val1) && remaining.count(known[val1])) {
            // We already know where the match is.
            int pos2 = known[val1];
            faceup(pos2);
            matched(pos1, pos2);
            remaining.erase(pos1);
            remaining.erase(pos2);
            known.erase(val1);
        } else {
            known[val1] = pos1;

            // Pick another unseen card.
            auto it2 = remaining.begin();
            if (*it2 == pos1) ++it2;
            if (it2 == remaining.end()) break; // shouldn't happen
            int pos2 = *it2;
            int val2 = faceup(pos2);

            if (val1 == val2) {
                matched(pos1, pos2);
                remaining.erase(pos1);
                remaining.erase(pos2);
                known.erase(val1);
            } else {
                known[val2] = pos2;
            }
        }
    }
}

// Stub main for standalone compilation.
int main() {
    int N;
    cin >> N;
    play(N);
    return 0;
}

int faceup(int /*pos*/) { return 0; }
void matched(int /*pos1*/, int /*pos2*/) {}

Source Files

Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.

TeX write-up competitive_programming/ioi/2010/memory/solution.tex

Exact copied write-up source.

Raw file
\documentclass[11pt,a4paper]{article}
\usepackage[utf8]{inputenc}
\usepackage[margin=1in]{geometry}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{hyperref}

\newtheorem{theorem}{Theorem}
\newtheorem{lemma}[theorem]{Lemma}

\lstset{
  language=C++,
  basicstyle=\ttfamily\small,
  keywordstyle=\color{blue}\bfseries,
  commentstyle=\color{green!60!black},
  stringstyle=\color{red!70!black},
  numbers=left,
  numberstyle=\tiny\color{gray},
  breaklines=true,
  frame=single,
  tabsize=4,
  showstringspaces=false
}

\title{IOI 2010 -- Memory}
\author{Solution}
\date{}

\begin{document}
\maketitle

\section{Problem Summary}
An interactive memory card game. There are $2N$ face-down cards forming $N$ matching pairs. Each turn, flip two cards. If they match, they are removed. Otherwise, they are flipped back. The goal is to match all pairs using as few flips as possible.

\section{Solution}

\subsection{Algorithm}
Maintain a map from each card value to its known position. Process cards using a \emph{known-first} strategy:

\begin{enumerate}
    \item Pick an unflipped card at position $p_1$ and flip it to reveal value $v_1$.
    \item If $v_1$ was previously seen at a known position $p_2$ (still unmatched), flip $p_2$ as the second card and collect the match.
    \item Otherwise, record $v_1$'s position. Pick another unseen card at position $p_2$ and flip it to reveal $v_2$.
    \begin{itemize}
        \item If $v_1 = v_2$, collect the match.
        \item Otherwise, record $v_2$'s position. Both cards flip back.
    \end{itemize}
\end{enumerate}

\subsection{Analysis}

\begin{theorem}
This strategy uses at most $3N$ flips.
\end{theorem}
\begin{proof}
Each pair is discovered and matched in one of two ways:
\begin{itemize}
    \item \emph{Lucky match} (step 3): both unknown cards happen to match. Cost: 2 flips.
    \item \emph{Known match} (step 2): the first card matches a previously seen card. Cost: 2 flips, but one of the pair was already flipped once before (during an earlier failed attempt costing 2 flips for that earlier turn). Hence the amortized cost per pair is at most $2 + 1 = 3$ flips.
\end{itemize}
In the worst case, every pair is first seen in a failed attempt (1 flip each card = 2 flips wasted) and then matched in a subsequent turn (2 flips). But each failed turn reveals two cards, contributing to at most two future matches. A careful accounting shows at most $3N$ total flips.
\end{proof}

\subsection{Complexity}
\begin{itemize}
    \item \textbf{Flips:} $O(N)$.
    \item \textbf{Time:} $O(N)$ with hash map lookups.
    \item \textbf{Space:} $O(N)$.
\end{itemize}

\section{Implementation}

\begin{lstlisting}
#include <bits/stdc++.h>
using namespace std;

// Grader functions
int faceup(int pos);            // flip card, return value
void matched(int pos1, int pos2); // declare a match

void play(int N) {
    unordered_map<int, int> known; // value -> position of a seen card
    set<int> remaining;
    for (int i = 1; i <= 2 * N; i++) remaining.insert(i);

    while (!remaining.empty()) {
        int pos1 = *remaining.begin();
        int val1 = faceup(pos1);

        if (known.count(val1) && remaining.count(known[val1])) {
            // Match with a previously seen card
            int pos2 = known[val1];
            faceup(pos2);
            matched(pos1, pos2);
            remaining.erase(pos1);
            remaining.erase(pos2);
            known.erase(val1);
        } else {
            known[val1] = pos1;

            // Pick another unseen card
            auto it = remaining.begin();
            if (*it == pos1) ++it;
            if (it == remaining.end()) break;
            int pos2 = *it;
            int val2 = faceup(pos2);

            if (val1 == val2) {
                matched(pos1, pos2);
                remaining.erase(pos1);
                remaining.erase(pos2);
                known.erase(val1);
            } else {
                known[val2] = pos2;
            }
        }
    }
}
\end{lstlisting}

\end{document}