← Back to the problem

USA USAAIO 2026 · Correction by IOAI Training

Matrix of a Linear Map and Rank-One Decomposition

This page gives the full approach. Read it once you have searched.

Metric
written answers (10 points)
Evaluated on
written answers
Our score
A and I = 3 decomposition verified
Expected level
correct matrix and justified decomposition

Expected level reached

We solved the problem ourselves: downloaded the data, ran the experiments and measured the score below. The statement and data belong to their organisers.

The problem in brief

A linear map takes a vector x=(x0,x1,x2,x3)x = (x_0, x_1, x_2, x_3) of R4\mathbb{R}^4 to the vector (x2−x3, x3, x0+2x1)(x_2 - x_3,\ x_3,\ x_0 + 2x_1) of R3\mathbb{R}^3. First you must write down the 3×43 \times 4 matrix AA of this map. Then you must express AA as a sum of II outer products u(i)v(i)⊤u^{(i)} v^{(i)\top} (rank-one matrices), giving II and every vector, with a justification. This is a short linear-algebra warm-up from Round 1 of the 2026 USA AI Olympiad, but the second part hides the idea behind PCA, SVD and low-rank compression of neural networks.

How you are scored

Each part is worth 5 points. Part 3.1 only needs the matrix. Part 3.2 needs II, the vectors and the reasoning. The decomposition is not unique, so a good answer gives one valid decomposition, checks it, and explains why its number of terms is the natural one.

Our answers (all verified in solution.py):

A=(001−100011200),I=3 (the rank of A, the smallest possible).A = \begin{pmatrix} 0 & 0 & 1 & -1 \\ 0 & 0 & 0 & 1 \\ 1 & 2 & 0 & 0 \end{pmatrix}, \qquad I = 3 \ (\text{the rank of } A,\ \text{the smallest possible}).

Question by question

3.1 The matrix

The cleanest way to read a matrix off a linear map is: column jj of AA is the image of the jj-th basis vector eje_j, because AejA e_j picks out column jj. Plug in each basis vector:

inputx2−x3x_2 - x_3x3x_3x0+2x1x_0 + 2x_1column
e0=(1,0,0,0)e_0 = (1,0,0,0)001(0,0,1)⊤(0,0,1)^\top
e1=(0,1,0,0)e_1 = (0,1,0,0)002(0,0,2)⊤(0,0,2)^\top
e2=(0,0,1,0)e_2 = (0,0,1,0)100(1,0,0)⊤(1,0,0)^\top
e3=(0,0,0,1)e_3 = (0,0,0,1)−1-110(−1,1,0)⊤(-1,1,0)^\top

Equivalently, row ii of AA lists the coefficients of the ii-th output: the first output is 0x0+0x1+1x2−1x30x_0 + 0x_1 + 1x_2 - 1x_3, and so on. Either way,

A=(001−100011200).A = \begin{pmatrix} 0 & 0 & 1 & -1 \\ 0 & 0 & 0 & 1 \\ 1 & 2 & 0 & 0 \end{pmatrix}.

Quick check with x=(1,1,1,1)x = (1,1,1,1): Ax=(0,1,3)Ax = (0, 1, 3), and indeed x2−x3=0x_2 - x_3 = 0, x3=1x_3 = 1, x0+2x1=3x_0 + 2x_1 = 3.

3.2 A sum of rank-one matrices

What an outer product is. For u∈R3u \in \mathbb{R}^3 and v∈R4v \in \mathbb{R}^4, the matrix uv⊤u v^\top has entries (uv⊤)jk=ujvk(u v^\top)_{jk} = u_j v_k: every row is a multiple of v⊤v^\top and every column is a multiple of uu. It has rank 1 (if u,v≠0u, v \neq 0). Acting on xx it gives u (v⊤x)u\,(v^\top x): "measure xx along vv, then output that amount in direction uu".

A decomposition with I=3I = 3 terms (row form). Write AA as the sum of its rows placed in their own position. With e0,e1,e2e_0, e_1, e_2 the standard basis of R3\mathbb{R}^3,

A=∑i=02ei (rowiA)⊤,A = \sum_{i=0}^{2} e_i\, (\text{row}_i A)^\top ,

that is,

u(0)=(100), v(0)=(001−1);u(1)=(010), v(1)=(0001);u(2)=(001), v(2)=(1200).u^{(0)} = \begin{pmatrix}1\\0\\0\end{pmatrix},\ v^{(0)} = \begin{pmatrix}0\\0\\1\\-1\end{pmatrix};\quad u^{(1)} = \begin{pmatrix}0\\1\\0\end{pmatrix},\ v^{(1)} = \begin{pmatrix}0\\0\\0\\1\end{pmatrix};\quad u^{(2)} = \begin{pmatrix}0\\0\\1\end{pmatrix},\ v^{(2)} = \begin{pmatrix}1\\2\\0\\0\end{pmatrix}.

The matrix A drawn as a heat map equals the sum of three matrices, each containing a single nonzero row of A: row 0 (0, 0, 1, -1), row 1 (0, 0, 0, 1), row 2 (1, 2, 0, 0).

Reading it as a map: term ii computes output number ii (namely v(i)⊤xv^{(i)\top}x) and writes it in slot ii (direction eie_i). This is exactly how the map was defined.

Why no decomposition uses fewer than 3 terms. Every column of ∑i<Iu(i)v(i)⊤\sum_{i<I} u^{(i)} v^{(i)\top} is a combination of u(0),…,u(I−1)u^{(0)}, \dots, u^{(I-1)}, so the column space has dimension at most II:

rank⁡(∑i=0I−1u(i)v(i)⊤)≤I.\operatorname{rank}\Big(\sum_{i=0}^{I-1} u^{(i)} v^{(i)\top}\Big) \le I .

Now rank⁡A=3\operatorname{rank} A = 3: columns 0, 2 and 3 are (0,0,1)(0,0,1), (1,0,0)(1,0,0) and (−1,1,0)(-1,1,0), and

det⁡(01−1001100)=1≠0,\det\begin{pmatrix} 0 & 1 & -1 \\ 0 & 0 & 1 \\ 1 & 0 & 0 \end{pmatrix} = 1 \neq 0,

so they are independent. Hence I≥3I \ge 3, and our decomposition is as short as possible. (Larger II is always possible, for instance by splitting a term into two halves, which is why the natural answer is the minimal one.)

Other valid answers. Grouping by columns gives A=∑j(coljA) ej⊤A = \sum_j (\text{col}_j A)\, e_j^\top with 4 terms, but column 1 is twice column 0, so those two merge into (0,0,1)⊤(1,2,0,0)(0,0,1)^\top(1,2,0,0):

A=(001) ⁣(1,2,0,0)+(100) ⁣(0,0,1,0)+(−110) ⁣(0,0,0,1).A = \begin{pmatrix}0\\0\\1\end{pmatrix}\!(1,2,0,0) + \begin{pmatrix}1\\0\\0\end{pmatrix}\!(0,0,1,0) + \begin{pmatrix}-1\\1\\0\end{pmatrix}\!(0,0,0,1).

The singular value decomposition gives a third, very special one, A=∑iσi u~(i)v~(i)⊤A = \sum_i \sigma_i\, \tilde u^{(i)} \tilde v^{(i)\top} with orthonormal u~\tilde u's and v~\tilde v's. Here AA⊤=(2−10−110005)AA^\top = \begin{pmatrix} 2 & -1 & 0\\ -1 & 1 & 0 \\ 0 & 0 & 5\end{pmatrix}, whose eigenvalues are 55 and 3±52\frac{3 \pm \sqrt5}{2}, so the singular values are 5≈2.236\sqrt5 \approx 2.236, 1+52≈1.618\frac{1+\sqrt5}{2} \approx 1.618 (the golden ratio) and 5−12≈0.618\frac{\sqrt5 - 1}{2} \approx 0.618.

The key idea

A rank-one matrix uv⊤u v^\top is the simplest possible linear map: one input direction, one output direction. The rank of a matrix is the minimum number of such pieces needed to build it, and that is why the answer to "what is II?" is a theorem rather than a guess.

Among all decompositions, the SVD orders the pieces by importance (their σi\sigma_i). Keeping only the first rr terms gives the best possible rank-rr approximation (Eckart-Young theorem); the error left in Frobenius norm is ∑i≥rσi2\sqrt{\sum_{i \ge r}\sigma_i^2}. For our AA, the best 2-term approximation still misses by σ2≈0.618\sigma_2 \approx 0.618, which confirms numerically that no two outer products reproduce AA exactly. This idea drives PCA, word-embedding compression, recommender systems and LoRA finetuning, where a weight update ΔW\Delta W is forced to be a sum of a few outer products.

The full solution

solution.py builds AA by applying the map to e0,…,e3e_0, \dots, e_3, checks Ax=f(x)Ax = f(x) on 1000 random vectors, and verifies the three decompositions exactly:

A = np.column_stack([f(np.eye(4)[j]) for j in range(4)])     # column j = f(e_j)
U = [np.eye(3)[i] for i in range(3)]; V = [A[i] for i in range(3)]
assert np.array_equal(sum(np.outer(u, v) for u, v in zip(U, V)), A)
assert np.linalg.matrix_rank(A) == 3                           # so I = 3 is minimal

Results

checkoutcome
AA from basis vectors, tested on 1000 random xxmatches the map
row-form decomposition, I=3I = 3exact
column-form decomposition (columns 0 and 1 merged), I=3I = 3exact
SVD form, σ=(5, 1.618, 0.618)\sigma = (\sqrt5,\ 1.618,\ 0.618)exact up to rounding
best possible 2-term approximationerror 0.618>00.618 > 0, so I=2I = 2 is impossible

What to remember

  • Column jj of a matrix is the image of eje_j. This turns "find the matrix of a linear map" into four substitutions.
  • An outer product uv⊤u v^\top is a rank-one map: read along vv, write along uu. Any matrix is a sum of them (rows or columns give one immediately).
  • The minimal number of rank-one terms equals the rank, because the rank of a sum of II outer products is at most II.
  • The SVD is the decomposition that sorts terms by importance, which is what makes truncation (PCA, low-rank compression, LoRA) optimal.
Full code (solution.py)
"""USAAIO 2026 Round 1, Problem 3: matrix of a linear map and rank-one decomposition.

A x = (x2 - x3, x3, x0 + 2 x1).  We (1) read A off column by column,
(2) check it on random vectors, (3) verify a decomposition into I = rank(A) = 3
outer products (row form), an alternative column form, and the SVD form,
(4) draw the figure used in the correction.
Run:  python solution.py
"""
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def f(x):
    x0, x1, x2, x3 = x
    return np.array([x2 - x3, x3, x0 + 2 * x1])

# ---------------------------------------------- Part 3.1: column j = f(e_j)
E = np.eye(4)
A = np.column_stack([f(E[j]) for j in range(4)])
print("A =\n", A)
assert np.array_equal(A, [[0, 0, 1, -1], [0, 0, 0, 1], [1, 2, 0, 0]])
rng = np.random.default_rng(0)
for _ in range(1000):                       # linearity check on random inputs
    x = rng.normal(size=4)
    assert np.allclose(A @ x, f(x))

# ---------------------------------------------- Part 3.2: rank-one terms
print("rank(A) =", np.linalg.matrix_rank(A))     # 3, so I >= 3 terms needed

# (a) row form: u(i) = e_i in R^3, v(i) = i-th row of A
U_row = [np.eye(3)[i] for i in range(3)]
V_row = [A[i] for i in range(3)]
assert np.array_equal(sum(np.outer(u, v) for u, v in zip(U_row, V_row)), A)

# (b) column form: A = sum_j a_j e_j^T, then merge columns 0 and 1 (a_1 = 2 a_0)
U_col = [np.array([0, 0, 1]), np.array([1, 0, 0]), np.array([-1, 1, 0])]
V_col = [np.array([1, 2, 0, 0]), np.array([0, 0, 1, 0]), np.array([0, 0, 0, 1])]
assert np.array_equal(sum(np.outer(u, v) for u, v in zip(U_col, V_col)), A)

# (c) SVD form: u(i) = sigma_i * left vector, v(i) = right vector
Us, s, Vt = np.linalg.svd(A)
print("singular values:", s.round(4), " sigma^2 =", (s**2).round(4))
assert np.allclose(sum(s[i] * np.outer(Us[:, i], Vt[i]) for i in range(3)), A)
assert np.allclose(sorted(s**2), sorted([(3 - np.sqrt(5)) / 2, (3 + np.sqrt(5)) / 2, 5]))

# a sum of 2 rank-one matrices has rank <= 2: the best one (Eckart-Young)
A2 = sum(s[i] * np.outer(Us[:, i], Vt[i]) for i in range(2))
print("best 2-term error (Frobenius):", np.linalg.norm(A - A2).round(4), "= sigma_3")

# ---------------------------------------------- figure
plt.rcParams.update({"font.size": 12})
mats = [A] + [np.outer(u, v) for u, v in zip(U_row, V_row)]
titles = ["A", r"$e_0\,v^{(0)\top}$", r"$e_1\,v^{(1)\top}$", r"$e_2\,v^{(2)\top}$"]
fig, axs = plt.subplots(1, 7, figsize=(13, 3.4),
                        gridspec_kw={"width_ratios": [4, 0.7, 4, 0.7, 4, 0.7, 4]})
for k, sym in zip([1, 3, 5], ["=", "+", "+"]):
    axs[k].axis("off")
    axs[k].text(0.5, 0.5, sym, fontsize=26, ha="center", va="center")
for ax, M, t in zip(axs[::2], mats, titles):
    M = M + 0.0                              # avoid printing "-0"
    ax.imshow(M, cmap="RdBu", vmin=-2.2, vmax=2.2)
    for (i, j), val in np.ndenumerate(M):
        ax.text(j, i, f"{val:g}", ha="center", va="center", fontsize=13,
                color="black" if abs(val) < 1.5 else "white")
    ax.set_xticks(range(4)); ax.set_yticks(range(3))
    ax.set_xlabel("column (input $x_j$)"); ax.set_title(t)
axs[0].set_ylabel("row (output)")
fig.tight_layout()
fig.savefig("figures/row_decomposition.png", dpi=150)
print("all checks passed")