← Retour à l'épreuve

Indonesia IOAI selection 2025 · Correction IOAI Training

Essay Paper

Cette page donne toute la méthode. Lisez-la une fois que vous avez cherché.

Métrique
hand-marked written answers
Évaluée sur
written answers
Notre score
7 solved; Q5 T8,T10 differ from key

Nous avons résolu l'épreuve nous-mêmes : téléchargé les données, mené les expériences et mesuré le score ci-dessous. L'énoncé et les données appartiennent à leurs organisateurs. Cette correction est rédigée en anglais.

The problem in brief

This is the written paper of the 2025 Indonesian IOAI selection exhibition round. It has seven independent exercises, in Indonesian: a logic puzzle (two robots locating a star on a 6 by 6 board), reading a rule-based decision plot, gradient descent on the squared hinge loss, bigram probabilities, hand-made features for 5 by 5 binary images, choosing a metric for a rare disease, and eigenvectors with diagonalisation. Every answer below is derived from scratch and checked numerically by solution.py. No official answer key is published. Exercise 5 prints a "Key" label for each test image, and we compare our rule with it.

How you are scored

The paper is marked by hand, exercise by exercise, with partial credit for correct reasoning. Exercise 1 explicitly gives partial credit for eliminating some of the stars, even without the final answer. No points table or pass mark is published.

The full solution

1. The two robots and Aoi's star

The stars sit at C1, E1, B2, D2, A3, E3, B4, D4, F4, C5, A6, D6, F6. ROBOBAR knows the row, ROBOKOL knows the column, and each line of the dialogue is public, so both robots (and we) can delete candidates as they go.

The rule for "I don't know": a robot that knows the row would know the star if its row contained a single remaining candidate. So "I don't know" from ROBOBAR deletes every candidate that is alone in its row, and the same from ROBOKOL deletes every candidate alone in its column.

  1. ROBOBAR, "I don't know". Row 5 has a single star, so C5 goes.
  2. ROBOKOL, "I don't know". Column C now holds only C1, so C1 goes.
  3. ROBOBAR, "I don't know". Row 1 now holds only E1, so E1 goes.
  4. ROBOKOL, "I don't know". Column E now holds only E3, so E3 goes.

That leaves B2, D2, A3, B4, D4, F4, A6, D6, F6.

  1. ROBOBAR, "we could go on forever". Imagine the dialogue going on. Row 3 now has only A3, so a further "I don't know" would delete it. Then column A has only A6, which goes next. After that, no row or column holds a single candidate any more. The stuck set, where the conversation really would go on forever, is therefore S={B2,D2,B4,D4,F4,D6,F6}S=\{B2, D2, B4, D4, F4, D6, F6\}. ROBOBAR can only claim "forever" if every candidate in his row lies in SS. Row 6 contains A6, which would eventually be found. If his row were 3, he would already know. So his row is 2 or 4, and the candidates become B2, D2, B4, D4, F4.
  2. ROBOKOL, "now I know". Column B holds B2 and B4, and column D holds D2 and D4, so in either case he could not decide. Column F holds only F4.

Aoi's star is F4.

Which line of the dialogue removes each star

The pattern to remember: each public statement of ignorance is information. The "forever" statement is a statement about a fixed point of the elimination process, so you have to run that process to its end.

2. Decision plot

Measuring the figure's pixels (the colour changes fall exactly on the grid lines at 2, 6 and 8) gives these rules:

  • if x1<6x_1 < 6: class 0 when x2<8x_2 < 8, class 2 otherwise;
  • if x1≥6x_1 \ge 6: class 0 when x2<2x_2 < 2, class 1 when 2≤x2<62 \le x_2 < 6, class 2 when x2≥6x_2 \ge 6.
  1. (8,3)(8, 3) has x1≥6x_1 \ge 6 and 2≤3<62 \le 3 < 6, so it is class 1.

  2. Classes 1 and 2 are separated by x2=6.00x_2 = 6.00 (for x1≥6x_1 \ge 6).

  3. Applying the rules to the 8 points: (5.86,3.62)(5.86, 3.62), (0.53,6.51)(0.53, 6.51), (5.69,7.46)(5.69, 7.46), (2.53,1.52)(2.53, 1.52) and (6.42,0.85)(6.42, 0.85) are class 0. (9.50,5.93)(9.50, 5.93) and (9.29,4.36)(9.29, 4.36) are class 1. (6.28,6.20)(6.28, 6.20) is class 2. So (a) 5, (b) 2, (c) 1. Watch (6.42,0.85)(6.42, 0.85): x1≥6x_1 \ge 6, but it falls in the blue strip under x2=2x_2 = 2.

  4. For a uniform point, probability is area divided by 100:

    • class 0 covers 6×8+4×2=566\times 8 + 4\times 2 = 56, so 0.56;
    • class 1 covers 4×4=164\times 4 = 16, so 0.16;
    • class 2 covers 6×2+4×4=286\times 2 + 4\times 4 = 28, so 0.28.

    They add up to 1, as they must.

3. Squared hinge loss

With f(x)=wx+bf(x)=wx+b and margin deficit mi=max⁡{0, 1−yif(xi)}m_i=\max\{0,\,1-y_i f(x_i)\}, the loss is L=15∑imi2L=\frac15\sum_i m_i^2.

Q1. At w=b=0w=b=0, every mi=1m_i = 1, so L=1L=1.

Q2. Where mi>0m_i>0, ∂mi/∂w=−yixi\partial m_i/\partial w = -y_i x_i and ∂mi/∂b=−yi\partial m_i/\partial b = -y_i. Where mi=0m_i=0, the term is flat. At the kink mi=0m_i = 0 both one-sided derivatives of mi2m_i^2 are 0, so the squared hinge is differentiable everywhere. That is its advantage over the plain hinge. Hence:

∂L∂w=−25∑i=15mi yi xi,∂L∂b=−25∑i=15mi yi.\frac{\partial L}{\partial w} = -\frac{2}{5}\sum_{i=1}^{5} m_i\, y_i\, x_i , \qquad \frac{\partial L}{\partial b} = -\frac{2}{5}\sum_{i=1}^{5} m_i\, y_i .

Q3. At the origin, all mi=1m_i=1. We have ∑yixi=−2.1+0.7−0.2−0.8+2.2=−0.2\sum y_i x_i = -2.1+0.7-0.2-0.8+2.2=-0.2 and ∑yi=−1\sum y_i = -1, so

∂L∂w=0.08,∂L∂b=0.4,\frac{\partial L}{\partial w} = 0.08, \qquad \frac{\partial L}{\partial b} = 0.4 ,

and one step with η=1\eta=1 gives w=−0.08w=-0.08, b=−0.4b=-0.4. The new margins 1−yif(xi)1-y_if(x_i) are 1.232,0.656,0.584,0.536,1.5761.232, 0.656, 0.584, 0.536, 1.576, so L=5.0603/5≈1.0121L = 5.0603/5 \approx 1.0121. The loss went up: η=1\eta = 1 is too large for this problem. A second step gives L≈1.142L\approx1.142, and if you keep going, the iterates diverge.

Q4. No. L=0L=0 would need f(−2.1)≥1f(-2.1)\ge 1 and f(2.2)≥1f(2.2) \ge 1 but f(0.2)≤−1f(0.2)\le -1. A linear ff evaluated at 0.20.2, which lies between −2.1-2.1 and 2.22.2, is a convex combination of the two end values, so it is at least 1. Contradiction. The dangerous cars are on both sides of the safe ones, so no single threshold on xx separates them. The best achievable loss, with a small learning rate, is about 0.9600.960. Gradient descent can at best converge to that minimum, never to 0.

Q5. Yes. With z=x2z=x^2, the dangerous points have z∈{4.41,4.84}z\in\{4.41, 4.84\} and the safe ones z∈{0.49,0.04,0.64}z\in\{0.49, 0.04, 0.64\}. These are separated by a threshold, with room for margin 1. For example, w=1w=1 and b=−2.5b=-2.5 give yif≥1.86y_if\ge1.86 for every point. Squaring the feature turned a two-sided rule ("too far left OR too far right") into a one-sided one.

Q6. On zz, with η=1\eta = 1:

  • t=1t=1: ∑yizi=8.08\sum y_i z_i = 8.08, so ∂L/∂w=−3.232\partial L/\partial w=-3.232 and ∂L/∂b=0.4\partial L/\partial b=0.4. This gives w(1)=3.232w^{(1)}=3.232, b(1)=−0.4b^{(1)}=-0.4 and L(1)≈2.4842L^{(1)}\approx 2.4842. The two dangerous points now have margin above 1, but the safe ones are pushed to the wrong side.
  • t=2t=2: only the three safe points still contribute. ∂L/∂w=0.4 (2.1837⋅0.49+0.7293⋅0.04+2.6685⋅0.64)=1.1228\partial L/\partial w = 0.4\,(2.1837\cdot0.49+0.7293\cdot0.04+2.6685\cdot0.64)=1.1228 and ∂L/∂b=0.4 (2.1837+0.7293+2.6685)=2.2326\partial L/\partial b = 0.4\,(2.1837+0.7293+2.6685)=2.2326. This gives w(2)≈2.1092w^{(2)}\approx 2.1092, b(2)≈−2.6326b^{(2)}\approx-2.6326 and L(2)=0L^{(2)} = 0: every point now has yif≥1y_i f \ge 1. For instance z=0.64z=0.64 gives f=−1.28f=-1.28, and z=4.41z=4.41 gives f=6.67f=6.67.

The same data in x and in z = x squared

4. Probability (bigrams)

Both first words were seen in 100 sentences, so they are equally likely a priori, and Bayes reduces to a ratio of counts.

  1. (a) P(lura∣domi)=4848+42=815≈0.533P(\text{lura}\mid\text{domi}) = \dfrac{48}{48+42} = \dfrac{8}{15} \approx 0.533. (b) P(kira∣salu)=2527+25=2552≈0.481P(\text{kira}\mid\text{salu}) = \dfrac{25}{27+25} = \dfrac{25}{52}\approx 0.481.
  2. The 10 sentences are independent, and each has first word "lura" with probability p=8/15p = 8/15, so the count is binomial:
P(X=6)=(106)(815)6(715)4≈0.229.P(X=6) = \binom{10}{6} \left(\tfrac{8}{15}\right)^{6} \left(\tfrac{7}{15}\right)^{4} \approx 0.229 .

5. Feature extraction

A1A_1 is a pattern of stripes (columns all 1 or all 0). A2A_2 is a checkerboard. Allowed distortions are rotation, shift and noise. A rotation by 90 degrees turns vertical stripes into horizontal ones and leaves a checkerboard unchanged. A one-pixel shift swaps the phase. So each class is a small family of perfect patterns: 4 stripe patterns (vertical or horizontal, 2 phases each) and 2 checkerboards.

Features (Q2).

  • dstripesd_{\text{stripes}} is the smallest number of pixels that differ from one of the 4 perfect stripe patterns.
  • dcheckerd_{\text{checker}} is the same count for the 2 checkerboards.

Both are computed directly from the entries, e.g. ∑i,j∣Mij−[ (j+p) mod 2=0 ]∣\sum_{i,j}\lvert M_{ij} - [\,(j+p) \bmod 2 = 0\,]\rvert. They generalise the statement's example feature "full column of ones": a perfect A1A_1 has dstripes=0d_{\text{stripes}}=0 because its columns are uniform.

imagedstripesd_{\text{stripes}}dcheckerd_{\text{checker}}nearest patternrule dstripes≤4d_{\text{stripes}}\le4printed key
T1010A1A1A1
T2010A1A1A1
T346A1A1A1
T464A2A2A2
T575A2A2A2
T6100A2A2A2
T7311A1A1A1
T8612A1A2A2
T928A1A1A1
T10612A1A2A2

Decision rule (Q1 and Q3). The natural rule, "pick the class whose nearest perfect pattern is closer", reproduces 8 of the 10 printed keys. T8 and T10 disagree with the key: both are 6 flips from horizontal stripes but 12 from any checkerboard, so the stated generation process (rotation + shift + noise) makes A1 the more plausible origin. The single rule that reproduces all ten printed keys is

label(T)={A1if dstripes(T)≤4,A2otherwise,\text{label}(T) = \begin{cases} A_1 & \text{if } d_{\text{stripes}}(T) \le 4,\\ A_2 & \text{otherwise,}\end{cases}

meaning: call it stripes only if at most 4 of the 25 pixels (16 %) break a stripe pattern. Because the threshold 4 was read off the keys themselves, we present it as the rule consistent with the answer sheet, and the nearest-pattern rule as the more principled one. T3 is worth a look: it is A1A_1 with rows 2 and 4 painted white. Painting the same rows white on A2A_2 gives exactly the same image, so T3 is ambiguous by construction. Only noise counts (4 versus 6 flips) favour A1.

6. Confusion matrix: choosing the metric

With 10 sick patients out of 1000, a system that always answers "healthy" scores 99 % accuracy while missing every patient, so accuracy is useless here. The two errors map onto two metrics:

  • a missed diagnosis is a false negative, which lowers recall =TP/(TP+FN)= TP/(TP+FN);
  • a false alarm is a false positive, which lowers precision =TP/(TP+FP)= TP/(TP+FP).

To weigh both in a balanced way, use the F1-score of the positive (sick) class:

F1=2PRP+R=2 TP2 TP+FP+FN.F_1 = \frac{2PR}{P+R} = \frac{2\,TP}{2\,TP + FP + FN} .

The second form shows the point: F1 counts FP and FN symmetrically and ignores the 990 true negatives that make accuracy look good. The harmonic mean is high only if both precision and recall are high. F1-micro is not the right choice: in binary classification it equals accuracy. F1-macro is not either: it averages in the F1 of the healthy class, which is close to 1 for any model and hides the failures. In practice you would also choose the decision threshold on a validation set, and use FβF_\beta with β>1\beta>1 if the hospital later decides that misses are worse than false alarms.

7. Matrices

Q1. For A=(1254)A=\begin{pmatrix}1&2\\5&4\end{pmatrix}:

det⁡(A−λI)=(1−λ)(4−λ)−10=λ2−5λ−6=(λ−6)(λ+1).\det(A-\lambda I) = (1-\lambda)(4-\lambda)-10 = \lambda^2-5\lambda-6 = (\lambda-6)(\lambda+1).
  • λ1=6\lambda_1=6: the first row of A−6IA - 6I gives −5v1+2v2=0-5v_1+2v_2=0, so v=(2,5)⊤v=(2,5)^\top (any nonzero multiple). Check: A(2,5)⊤=(12,30)⊤=6 (2,5)⊤A(2,5)^\top = (12,30)^\top = 6\,(2,5)^\top.
  • λ2=−1\lambda_2=-1: 2v1+2v2=02v_1+2v_2=0, so v=(1,−1)⊤v=(1,-1)^\top. Check: A(1,−1)⊤=(−1,1)⊤A(1,-1)^\top=(-1,1)^\top.

Q2. Suppose A=PDP−1A=PDP^{-1} with D=diag⁡(d1,d2)D=\operatorname{diag}(d_1,d_2) and PP invertible. Multiply on the right by PP: AP=PDAP=PD. Write P=[ p1 p2 ]P=[\,p_1\ p_2\,] by columns. The jj-th column of APAP is ApjAp_j. The jj-th column of PDPD is PP times the jj-th column of DD, that is P(djej)=djpjP(d_j e_j)=d_j p_j. Hence

Ap1=d1p1,Ap2=d2p2.A p_1 = d_1 p_1, \qquad A p_2 = d_2 p_2 .

Since PP is invertible, its columns are nonzero (a zero column would make det⁡P=0\det P=0). So each pjp_j is an eigenvector of AA with eigenvalue djd_j. In coordinates, the first column reads a11p11+a12p21=d1p11a_{11}p_{11}+a_{12}p_{21} = d_1 p_{11} and a21p11+a22p21=d1p21a_{21}p_{11}+a_{22}p_{21} = d_1 p_{21}, and the same holds for the second column. With the example of Q1: P=(215−1)P=\begin{pmatrix}2&1\\5&-1\end{pmatrix} and D=diag⁡(6,−1)D=\operatorname{diag}(6,-1) satisfy A=PDP−1A=PDP^{-1}.

Results

exerciseanswer
1. Logicthe star is at F4
2. Decision plotclass 1; boundary x2=6.00x_2=6.00; counts 5 / 2 / 1; probabilities 0.56 / 0.16 / 0.28
3. Squared hingeL(0,0)=1L(0,0)=1; step to (−0.08,−0.4)(-0.08,-0.4) with L≈1.0121L\approx1.0121; L=0L=0 impossible on xx (best 0.960); possible on z=x2z=x^2; (3.232,−0.4)(3.232,-0.4) with L≈2.4842L\approx2.4842, then (2.1092,−2.6326)(2.1092,-2.6326) with L=0L=0
4. Probability8/15≈0.5338/15\approx0.533; 25/52≈0.48125/52\approx0.481; 0.2290.229
5. Featuresnearest-pattern rule: 8 of 10 printed keys (T8, T10 differ); rule dstripes≤4d_{\text{stripes}}\le4 matches all 10
6. MetricF1-score of the positive class
7. Matricesλ=6\lambda=6 with (2,5)(2,5), λ=−1\lambda=-1 with (1,−1)(1,-1); proof via AP=PDAP=PD

What to remember

  • Statements of ignorance carry information, and "forever" means "in the fixed point of the elimination". Run the process to the end before reasoning about it.
  • A feature map can make a problem linearly separable. On xx, the two-sided rule "too far left or too far right" defeats any linear model. On x2x^2, one threshold is enough, and that is the idea behind kernels.
  • Gradient descent is not guaranteed to decrease the loss. With η=1\eta=1, the very first step increased it. Always check the loss after a step.
  • On rare classes, use F1 (or precision and recall), not accuracy. F1 equals 2TP/(2TP+FP+FN)2TP/(2TP+FP+FN): it treats both errors alike and ignores the many easy negatives.
  • Hand-made features should encode the invariances you are told about (here rotation and shift). When a rule is tuned to match a given answer key, say so, and keep the principled version next to it.
Code complet (solution.py)
"""Checks every numeric answer of the Indonesian IOAI selection 2025 essay paper (Ekshibisi, final).
Run: python solution.py   (numpy, scipy only)"""
import numpy as np
from math import comb
from collections import Counter

# ---------------------------------------------------------------- 1. the two robots and the stars
STARS = {'C1', 'E1', 'B2', 'D2', 'A3', 'E3', 'B4', 'D4', 'F4', 'C5', 'A6', 'D6', 'F6'}
row = lambda s: s[1]
col = lambda s: s[0]

def dont_know(cands, key):
    """'I don't know' from the robot who knows key(s): drop stars alone in their row/column."""
    n = Counter(key(s) for s in cands)
    return {s for s in cands if n[key(s)] > 1}

def stuck(cands):
    """Stars for which the alternating 'I don't know' could go on forever (fixed point)."""
    while True:
        new = dont_know(dont_know(cands, row), col)
        if new == cands: return cands
        cands = new

c = set(STARS)
for who in (row, col, row, col):          # four "I don't know" lines
    c = dont_know(c, who)
forever = stuck(c)
# ROBOBAR can only say "forever" if every candidate in his row is in the stuck set (and he does not know)
c = {s for s in c if all(t in forever for t in c if row(t) == row(s))
     and sum(row(t) == row(s) for t in c) > 1}
n = Counter(col(s) for s in c)
c = {s for s in c if n[col(s)] == 1}       # ROBOKOL: "now I know"
print('1. Aoi star:', c)

# ---------------------------------------------------------------- 2. decision plot (rules read off the figure)
def cls(x1, x2):
    if x1 < 6: return 0 if x2 < 8 else 2
    return 0 if x2 < 2 else (1 if x2 < 6 else 2)
data = [(5.86, 3.62), (0.53, 6.51), (9.50, 5.93), (5.69, 7.46), (6.28, 6.20), (2.53, 1.52), (6.42, 0.85), (9.29, 4.36)]
print('2. class of (8,3):', cls(8, 3), '| counts:', Counter(cls(*p) for p in data))
g = np.linspace(0, 10, 2001)[:-1] + 0.0025
G = np.array([[cls(a, b) for a in g] for b in g])
print('   areas:', [round((G == k).mean(), 2) for k in range(3)])

# ---------------------------------------------------------------- 3. squared hinge loss and gradient descent
x = np.array([-2.1, -0.7, 0.2, 0.8, 2.2]); y = np.array([1, -1, -1, -1, 1.])
def loss(w, b, f): return np.mean(np.maximum(0, 1 - y * (w * f + b)) ** 2)
def grad(w, b, f):
    m = np.maximum(0, 1 - y * (w * f + b))
    return np.mean(-2 * m * y * f), np.mean(-2 * m * y)
for name, f in (('x', x), ('z=x^2', x ** 2)):
    w = b = 0.0
    print(f'3. feature {name}: L(0,0) = {loss(0, 0, f):.4f}')
    for t in (1, 2):
        gw, gb = grad(w, b, f); w, b = w - gw, b - gb
        print(f'   t={t}: grad=({gw:.5f}, {gb:.5f})  w={w:.5f}  b={b:.5f}  L={loss(w, b, f):.6f}')
from scipy.optimize import minimize
print('   best possible L with feature x:', round(minimize(lambda p: loss(p[0], p[1], x), [0, 0]).fun, 4))

# ---------------------------------------------------------------- 4. bigram probabilities
p_lura_domi = 48 / (48 + 42); p_kira_salu = 25 / (27 + 25)
print(f'4. P(lura|domi)={p_lura_domi:.4f}  P(kira|salu)={p_kira_salu:.4f}  '
      f'P(6 of 10 lura)={comb(10, 6) * p_lura_domi**6 * (1 - p_lura_domi)**4:.4f}')

# ---------------------------------------------------------------- 5. feature extraction
M = lambda s: np.array([[int(ch) for ch in r] for r in s.split()])
T = ['01010 01010 01010 01010 01010', '11111 00000 11111 00000 11111', '10101 11111 10101 11111 10101',
     '10101 11110 10101 01111 10101', '11010 11111 01010 11111 01010', '01010 10101 01010 10101 01010',
     '11111 10000 11111 00011 11111', '11111 10111 11111 10010 11111', '10101 11101 10101 10111 10101',
     '01100 11111 10001 11111 01100']
KEY = ['A1', 'A1', 'A1', 'A2', 'A2', 'A2', 'A1', 'A2', 'A1', 'A2']
I, J = np.indices((5, 5))
stripes = [((J + p) % 2 == 0) for p in (0, 1)] + [((I + p) % 2 == 0) for p in (0, 1)]   # A1 rotated / shifted
checker = [((I + J + p) % 2 == 0) for p in (0, 1)]                                    # A2 shifted
for i, (s, k) in enumerate(zip(T, KEY), 1):
    m = M(s)
    ds = min(int((m != t).sum()) for t in stripes); dc = min(int((m != t).sum()) for t in checker)
    nearest = 'A1' if ds < dc else 'A2'
    rule = 'A1' if ds <= 4 else 'A2'
    print(f'5. T{i}: d_stripes={ds:2d} d_checker={dc:2d} nearest={nearest} rule(d_stripes<=4)={rule} key={k}')

# ---------------------------------------------------------------- 7. eigenvalues
A = np.array([[1, 2], [5, 4.]])
vals, vecs = np.linalg.eig(A)
print('7. eigenvalues', vals, '| A v = lambda v:', [np.allclose(A @ v, l * v) for l, v in zip(vals, vecs.T)])
print('   check (2,5) and (1,-1):', A @ [2, 5], A @ [1, -1])