IOI 2026 — Day 1

Bộ đề bài

# Bài tập Điểm Thời gian: Giới hạn bộ nhớ
1 IOI 2026 Ngày 1 Bài 1 - Ball Machine 100 (p) 1.0s 2G
2 IOI 2026 Ngày 1 Bài 2 - Monuments 100 (p) 1.0s 2G
3 IOI 2026 Ngày 1 Bài 3 - Tiling Game 100 (p) 1.0s 2G

1. IOI 2026 Ngày 1 Bài 1 - Ball Machine

Điểm: 100 (p) Thời gian: 1.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Madina đã phát minh ra một máy bắn bóng để giải trí cho những thí sinh của IOI. Cấu trúc bên trong của máy như sau:

  • Máy này bao gồm \(N\) nút, được đánh số từ \(0\) đến \(N - 1\). Nút \(N - 1\) được gọi là nút gốc.
  • Với mỗi nút \(u\) mà \(0 \le u < N - 1\), nút cha của nút \(u\) là một nút \(P[u]\) có \(P[u] > u\), và nút \(u\) là nút con của \(P[u]\). Nút gốc không có nút cha. Một nút không có nút con được gọi là nút lá.

Bạn không biết \(N\) và không biết nút cha \(P[u]\) của bất kỳ nút \(u\) nào. Thay vào đó, Madina cho bạn biết \(M\) là số lượng nút lá, và chỉ số của các nút lá là \(0, 1, \ldots, M - 1\). Nhiệm vụ của bạn là xác định cấu trúc bên trong của máy bằng cách vận hành nó.

Mỗi nút của máy có thể chứa tối đa một quả bóng, và mỗi quả bóng có một giá trị là một số nguyên không âm. Ta nói rằng một nút có giá trị \(x\) nếu nó chứa một quả bóng có giá trị \(x\). Một nút được coi là bị chiếm dụng nếu nó chứa một quả bóng; ngược lại, nó là trống. Ban đầu, mọi nút đều trống.

Bạn có thể thực hiện hai loại thao tác:

  • insert(U, X): Chèn một quả bóng mới có giá trị là \(X\) vào nút lá \(U\).
    • Nếu nút lá \(U\) đã bị chiếm dụng, thao tác sẽ trả về false và không chèn quả bóng.
    • Nếu nút lá \(U\) trống, quả bóng được đặt tại nút \(U\). Sau đó, quả bóng liên tục di chuyển đến nút cha của nút hiện tại miễn là nút cha đó trống. Quá trình di chuyển dừng lại khi quả bóng đến nút gốc hoặc một nút có nút cha đang bị chiếm dụng. Thao tác trả về true.
  • collect(): Nếu nút gốc trống (nghĩa là máy trống), hàm collect trả về một mảng rỗng. Ngược lại, hàm đệ quy traverse được mô tả bên dưới sẽ thu thập giá trị của tất cả các quả bóng hiện có trong máy vào một mảng \(S\). Ban đầu, mảng \(S\) trống và traverse(N - 1) được gọi.
C++
traverse(u):
    thêm giá trị của quả bóng tại nút u vào cuối S
    gọi c[u] là danh sách các nút con bị chiếm dụng của nút u
    sắp xếp c[u] theo thứ tự không giảm dựa trên giá trị
        của các quả bóng tại các nút đó (nếu có nhiều nút giá trị giống
        nhau, chúng có thể xuất hiện theo bất kỳ thứ tự nào)
    for mỗi v thuộc c[u]:
        traverse(v)

Sau khi hàm này kết thúc, tất cả các quả bóng sẽ được lấy ra khỏi máy và collect sẽ trả về \(S\).

Ví dụ, xét một máy có \(N = 7\) nút và mảng cha \(P = [4, 6, 5, 5, 6, 6]\). Hình bên trái hiển thị máy trống với các chỉ số nút, trong khi hình bên phải cho thấy một cấu hình khả thi của các quả bóng sau một dãy các thao tác insert (dãy này được đưa ra trong phần Ví dụ).

Khi collect() được gọi, nút gốc (nút \(6\)) bị chiếm dụng, do đó \(S\) được khởi tạo là \(S = []\) và traverse(6) được gọi.

traverse(6): Nút \(6\) có giá trị \(0\); thêm nó vào \(S\) sẽ cho \(S = [0]\). Các nút con của nút \(6\) là \(4\), \(1\) và \(5\), tất cả đều đã được chiếm dụng. Giá trị của chúng lần lượt là \(20\), \(10\) và \(20\). Sắp xếp theo thứ tự không giảm sẽ cho \(c[6] = [1, 5, 4]\) (lưu ý rằng \(c[6] = [1, 4, 5]\) cũng hợp lệ, vì các nút \(4\) và \(5\) có cùng giá trị). Sau đó, hàm này gọi traverse trên từng nút trong \(c[6]\) theo thứ tự đó.

  • traverse(1): Nút \(1\) có giá trị \(10\); thêm nó vào \(S\) sẽ cho \(S = [0, 10]\). Nút \(1\) không có nút con nào bị chiếm dụng, vì vậy lệnh gọi này kết thúc.
  • traverse(5): Nút \(5\) có giá trị \(20\); thêm nó vào \(S\) sẽ cho \(S = [0, 10, 20]\). Nút \(5\) có một nút con bị chiếm dụng là nút \(2\), vì vậy \(c[5] = [2]\) và hàm này gọi traverse(2).
    • traverse(2): Nút \(2\) có giá trị \(30\); thêm nó vào \(S\) sẽ cho \(S = [0, 10, 20, 30]\). Nút \(2\) không có nút con nào bị chiếm dụng, vì vậy lệnh gọi này kết thúc.
    • Quá trình thực thi quay trở lại traverse(5) đã xử lý tất cả các phần tử con trong \(c[5]\), do đó lệnh gọi hàm này kết thúc.
  • traverse(4): Nút \(4\) có giá trị \(20\); thêm nó vào \(S\) sẽ cho \(S = [0, 10, 20, 30, 20]\). Nút \(4\) không có nút con nào bị chiếm dụng, vì vậy lệnh gọi này kết thúc.

Tất cả các lệnh gọi đã kết thúc và không còn nút nào cần xử lý nữa. Cuối cùng, tất cả các quả bóng được lấy ra khỏi máy và collect trả về mảng \(S = [0, 10, 20, 30, 20]\).

Nhiệm vụ của bạn là xác định số lượng nút \(N\) và trả về một mảng các nút cha \(R = [R[0], R[1], \ldots, R[N - 2]]\) mô tả cấu trúc của máy, nhưng với cách đánh chỉ số có thể khác cho các nút không phải là nút lá và không phải là nút gốc. Một cách hình thức, một mảng \(R\) được trả về sẽ được coi là đúng nếu có thể đánh các chỉ số đôi một khác nhau \(L[u]\) với \(0 \le L[u] < N\) cho mỗi nút \(u\) của máy sao cho:

  • \(L[u] = u\) với mọi \(0 \le u < M\) và với \(u = N - 1\); và
  • \(L[P[u]] = R[L[u]]\) với mọi \(0 \le u < N - 1\).

Không bắt buộc \(R[u] > u\). Máy phải trống khi bạn trả về lời giải.

Gọi \(K\) là số lần thực hiện thao tác collect, và \(B\) là giá trị lớn nhất của bất kỳ quả bóng nào trong tất cả các lần gọi insert. Gọi \(C = K + B\). Khi đó, \(C\) không được vượt quá \(1000\). Điểm của bạn trong một số subtask phụ thuộc vào giá trị của \(C\).

Chi tiết cài đặt

Bạn cần xây dựng hàm sau.

C++
std::vector<int> find_structure(int M)
  • \(M\): Số lượng nút lá trong máy.
  • Hàm này được gọi đúng một lần cho mỗi trường hợp test.
  • Hàm này phải trả về một mảng \(R = [R[0], R[1], \ldots, R[N - 2]]\) có độ dài \(N - 1\), là một mảng cha đúng.

Để tương tác với máy, hàm của bạn có thể gọi hai hàm sau.

Hàm thứ nhất là:

C++
bool insert(int U, int X)
  • \(U\): Chỉ số của nút lá mà quả bóng cần được chèn vào. Điều kiện \(0 \le U < M\) phải được thỏa mãn.
  • \(X\): Giá trị nguyên của quả bóng. Điều kiện \(0 \le X \le 1000\) phải được thỏa mãn.
  • Hàm này trả về true nếu quả bóng được đặt thành công, hoặc false nếu nút lá \(U\) đã bị chiếm dụng.
  • Hàm này có thể được gọi tối đa \(500\,000\) lần.

Hàm thứ hai là:

C++
std::vector<int> collect()

Thu thập giá trị của tất cả các quả bóng đã được đưa vào, làm trống máy và trả về mảng \(S\) đã thu thập.

Nếu tại bất kỳ thời điểm nào trong quá trình thực thi chương trình, giá trị của \(C\) vượt quá \(1000\), giải pháp của bạn sẽ nhận được phản hồi Output isn't correct: Too many resources used.

Cấu trúc của máy được cố định trước khi hàm find_structure được gọi. Trình chấm điểm là đơn định theo nghĩa nếu bạn chạy nó hai lần và cả hai lần chạy đều thực hiện cùng một dãy các thao tác, thì các lệnh gọi đến collect sẽ trả về cùng một mảng.

Các ràng buộc

  • \(2 \le N \le 1000\).
  • \(1 \le M \le 200\).
  • \(M < N\).

Các subtask

Subtask Điểm Các ràng buộc thêm
1 5 Nút gốc có đúng \(M\) nút con.
2 10 \(M \le 3\)
3 25 \(N \le 200\), \(M \le 45\)
4 60 Không có ràng buộc nào thêm.

Trong các subtask \(3\) và \(4\), điểm số của bạn phụ thuộc vào giá trị của \(C\) như sau.

Subtask 3 (25 điểm)

Giới hạn Điểm
\(1000 < C\) \(0\)
\(45 < C \le 1000\) \(13\)
\(C \le 45\) \(25\)

Subtask 4 (60 điểm)

Giới hạn Điểm
\(1000 < C\) \(0\)
\(200 < C \le 1000\) \(7\)
\(71 < C \le 200\) \(47 - \frac{C}{5}\)
\(44 < C \le 71\) \(104 - C\)
\(C \le 44\) \(60\)

Ví dụ

Xét cùng một máy như trong mô tả nhiệm vụ, với \(N = 7\), \(M = 4\) và \(P = [4, 6, 5, 5, 6, 6]\). Máy đó được minh họa lại trong hình sau:

Trình chấm điểm gọi hàm sau:

C++
find_structure(4)

Hàm có thể thực hiện dãy cuộc gọi sau:

  1. insert(0, 0): Nút lá \(0\) trống, nên một quả bóng có giá trị \(0\) được đặt tại lá \(0\). Nút \(P[0] = 4\) trống, nên quả bóng di chuyển đến nút \(4\). Nút \(P[4] = 6\) trống, nên quả bóng di chuyển đến nút \(6\). Vì nút \(6\) là nút gốc, nên quá trình di chuyển dừng lại. Lệnh gọi trả về true.
  2. insert(3, 20): Nút lá \(3\) đang trống, nên một quả bóng có giá trị \(20\) được đặt tại lá \(3\). Nút \(P[3] = 5\) đang trống, nên quả bóng di chuyển đến nút \(5\). Vì \(P[5] = 6\) đang bị chiếm dụng, nên quá trình di chuyển dừng lại. Lệnh gọi trả về true.
  3. insert(1, 10): Nút lá \(1\) trống, vì vậy một quả bóng có giá trị \(10\) được đặt tại lá \(1\). Vì \(P[1] = 6\) đã bị chiếm dụng, quả bóng vẫn ở nút \(1\). Lệnh gọi trả về true.
  4. insert(2, 30): Nút lá \(2\) đang trống, vì vậy một quả bóng có giá trị \(30\) được đặt tại lá \(2\). Vì \(P[2] = 5\) đã bị chiếm dụng, quả bóng vẫn ở nút \(2\). Lệnh gọi trả về true.
  5. insert(1, 25): Nút lá \(1\) đã bị chiếm dụng, do đó quả bóng không được đặt tại nút lá \(1\). Lệnh gọi trả về false.
  6. insert(0, 20): Nút lá \(0\) đang trống, nên một quả bóng có giá trị \(20\) được đặt tại nút lá \(0\). Nút \(P[0] = 4\) đang trống, nên quả bóng di chuyển đến nút \(4\). Vì \(P[4] = 6\) đã bị chiếm dụng, nên quá trình di chuyển dừng lại. Lệnh gọi trả về true.

Cấu hình các quả bóng thu được đã được thể hiện trong phần mô tả nhiệm vụ.

Tiếp theo, hàm có thể gọi:

C++
collect()

Việc thực thi lệnh gọi này đã được giải thích trong phần mô tả nhiệm vụ, vì vậy lệnh gọi này trả về \(S = [0, 10, 20, 30, 20]\). Sau đó, máy lại trở về trạng thái trống.

Tiếp theo, hàm có thể gọi insert(2, 25). Nút lá \(2\) đang trống, vì vậy một quả bóng có giá trị \(25\) được đặt tại nút lá \(2\). Quả bóng này sau đó di chuyển đến nút \(5\), rồi đến nút \(6\), nơi nó dừng lại. Lệnh gọi trả về true. Cấu hình các quả bóng sau khi thực hiện được thể hiện trong hình dưới đây.

Cuối cùng, có thể gọi hàm collect(), hàm này trả về giá trị \(S = [25]\) và làm trống máy.

Có hai mảng cha hợp lệ mà hàm find_structure có thể trả về: \(R = P = [4, 6, 5, 5, 6, 6]\) (tương ứng với cách đánh chỉ số \(L = [0, 1, 2, 3, 4, 5, 6]\)) và \(R = [5, 6, 4, 4, 6, 6]\) (tương ứng với cách đánh chỉ số \(L = [0, 1, 2, 3, 5, 4, 6]\)). Trong ví dụ này, \(K = 2\) và \(B = 30\), do đó \(C = 32\).

Trình chấm mẫu

Định dạng dữ liệu vào:

N M
P[0] P[1] P[2] ... P[N-2]

Định dạng kết quả ra:

K B C
Q
R[0] R[1] R[2] ... R[Q-1]

Trong đó, \(Q\) là kích thước của mảng \(R\) được trả về bởi find_structure.


Nguồn: IOI 2026, Ngày 1 — Ball Machine (ballmachine), bản đề chính thức tiếng Việt.

2. IOI 2026 Ngày 1 Bài 2 - Monuments

Điểm: 100 (p) Thời gian: 1.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Quảng trường Registan nổi tiếng ở Samarkand có \(N\) di tích được xếp thành một hàng chạy ngang qua trung tâm quảng trường. Các di tích được đánh số từ \(0\) đến \(N - 1\). Cột mốc \(i\) (với \(0 \le i < N\)) ban đầu nằm ở tọa độ nguyên \(X[i]\). Tâm của quảng trường nằm ở tọa độ \(0\).

Các kiến trúc sư yêu cầu sự đối xứng hoàn hảo so với tâm. Họ muốn di chuyển một số (có thể là không) di tích sao cho bố cục cuối cùng đối xứng qua tọa độ \(0\). Một cách hình thức:

  • Mỗi di tích phải được đặt tại các tọa độ nguyên.
  • Mỗi tọa độ nguyên có thể chứa không, một hoặc nhiều di tích.
  • Đối với mọi số nguyên \(x > 0\), số lượng di tích tại tọa độ \(x\) phải bằng số lượng di tích tại tọa độ \(-x\). Bất kỳ số lượng di tích nào (có thể là không) đều có thể được đặt tại tọa độ \(0\).

Tuy nhiên, có \(M\) di tích là những di tích cổ xưa và rất dễ hỏng nên không thể di dời. Chỉ số của chúng là \(P[0], P[1], \ldots, P[M - 1]\). \(M\) di tích này phải được giữ nguyên tại tọa độ ban đầu. \(N - M\) di tích còn lại có thể được di dời đến bất kỳ tọa độ nguyên nào. Các di tích không gây cản trở lẫn nhau trong quá trình di chuyển.

Chi phí để di dời một di tích từ tọa độ \(a\) đến tọa độ \(b\) là độ chênh lệch tuyệt đối \(\lvert a - b \rvert\). Tổng chi phí là tổng của tất cả các chi phí di chuyển các di tích.

Nhiệm vụ của bạn là tìm tổng chi phí di chuyển nhỏ nhất để các vị trí của các di tích đối xứng qua tọa độ \(0\), hoặc xác định rằng không thể đạt được sự đối xứng như vậy với các ràng buộc đã cho.

Chi tiết cài đặt

Bạn cần cài đặt hàm sau:

C++
long long get_cost(std::vector<int> X, std::vector<int> P)
  • \(X\): Một mảng sắp xếp không giảm có độ dài \(N\) mô tả tọa độ ban đầu của các di tích.
  • \(P\): Một mảng sắp xếp tăng ngặt có độ dài \(M\) mô tả các chỉ số của các di tích cổ.
  • Hàm này được gọi đúng một lần cho mỗi trường hợp test.

Hàm phải trả về một số nguyên: tổng chi phí ít nhất để làm cho các vị trí trở nên đối xứng, hoặc \(-1\) nếu điều đó là không thể.

Các ràng buộc

  • \(1 \le N \le 500\,000\).
  • \(0 \le M \le N\).
  • \(-10^9 \le X[0] \le X[1] \le \ldots \le X[N - 1] \le 10^9\).
  • \(0 \le P[0] < P[1] < \ldots < P[M - 1] < N\).

Các subtask

Subtask Điểm Các ràng buộc thêm
1 3 \(M = N\)
2 4 \(M = 0\)
3 5 \(X[P[j]] < 0\) với mỗi \(j\) sao cho \(0 \le j < M\).
4 6 \(N \le 10\)
5 5 \(N \le 19\)
6 5 \(N \le 32\)
7 13 \(N \le 200\)
8 17 \(N \le 4000\)
9 13 \(M \le 4000\)
10 29 Không có ràng buộc nào thêm.

Các ví dụ

Ví dụ 1

Xét lời gọi hàm sau:

C++
get_cost([-3, -2, 1, 3], [1, 2])

Vị trí ban đầu của các di tích được thể hiện trong hình dưới đây.

Có \(N = 4\) di tích, ban đầu nằm tại các tọa độ \([-3, -2, 1, 3]\). Các chỉ số của các di tích cổ là \(P[0] = 1\) và \(P[1] = 2\). Tức là, các di tích tại các tọa độ \(X[P[0]] = -2\) và \(X[P[1]] = 1\) không thể di chuyển. Các di tích còn lại tại các tọa độ \(-3\) và \(3\) có thể được di chuyển.

Để đạt được tính đối xứng với tổng chi phí nhỏ nhất, ta nên di chuyển các di tích như sau:

  • Di chuyển di tích từ tọa độ \(-3\) đến \(-1\), với chi phí là \(\lvert (-3) - (-1) \rvert = 2\).
  • Di chuyển di tích từ tọa độ \(3\) đến \(2\), với chi phí là \(\lvert 3 - 2 \rvert = 1\).

Sau các thao tác di chuyển này, vị trí các di tích trở nên đối xứng.

Tổng chi phí di chuyển là \(2 + 1 = 3\), do đó hàm cần trả về \(3\).

Ví dụ 2

Xét lời gọi hàm sau:

C++
get_cost([2, 2, 2, 3], [])

Trong đó, \(M = 0\), có nghĩa là không có di tích nào là cổ xưa, và tất cả các di tích đều có thể được di dời. Một giải pháp có tổng chi phí tối thiểu là di dời tất cả các di tích đến tọa độ \(0\). Chi phí cho mỗi di tích là:

  • \(\lvert 2 - 0 \rvert = 2\) đối với các di tích \(0\), \(1\) và \(2\).
  • \(\lvert 3 - 0 \rvert = 3\) đối với di tích \(3\).

Tổng chi phí là \(2 + 2 + 2 + 3 = 9\). Do đó, thủ tục cần trả về \(9\). Lưu ý rằng có các giải pháp khác có cùng tổng chi phí.

Ví dụ 3

Xét lời gọi hàm sau:

C++
get_cost([1, 2, 3, 4], [0, 1, 2, 3])

Tất cả \(4\) di tích đều là di tích cổ và không thể di dời. Vì tọa độ của chúng đều là số dương, nên không thể tạo ra một cấu hình đối xứng qua trục \(0\). Do đó, hàm cần trả về kết quả là \(-1\).

Trình chấm mẫu

Định dạng dữ liệu vào:

N M
X[0] X[1] ... X[N-1]
P[0] P[1] ... P[M-1]

Lưu ý rằng dòng thứ ba có thể trống nếu \(M\) bằng \(0\).

Định dạng kết quả ra:

C

Trong đó, \(C\) là giá trị trả về bởi hàm get_cost.


Nguồn: IOI 2026, Ngày 1 — Monuments (monuments), bản đề chính thức tiếng Việt.

3. IOI 2026 Ngày 1 Bài 3 - Tiling Game

Điểm: 100 (p) Thời gian: 1.0s Bộ nhớ: 2G Input: bàn phím Output: màn hình

Barchin và Charos đang chơi một trò chơi trên một lưới gồm \(2N \times 2M\) ô vuông đơn vị. Các hàng được đánh số từ \(0\) đến \(2N - 1\) từ trên xuống dưới, và các cột được đánh số từ \(0\) đến \(2M - 1\) từ trái sang phải. Với \(0 \le i < 2N\) và \(0 \le j < 2M\), ta ký hiệu ô ở hàng \(i\) và cột \(j\) là \((i, j)\).

Barchin đưa cho Charos lần lượt \(N \cdot M\) khối. Mỗi khối là một hình vuông \(2 \times 2\) gồm bốn viên gạch \(1 \times 1\). Barchin đã tô màu đen hoặc trắng cho mọi viên gạch trong khối, đồng thời đảm bảo rằng ít nhất một viên gạch có màu trắng.

Charos phải đặt mỗi khối lên lưới ngay lập tức sau khi nhận được, mà không biết mình sẽ nhận được những khối nào sau đó. Các khối không thể xoay. Mỗi khối phải được đặt hoàn toàn bên trong lưới, che phủ chính xác bốn ô lưới. Hơn nữa, viên gạch trên cùng bên trái của mỗi khối phải che phủ một ô có tọa độ hàng và cột đều là số chẵn. Mỗi ô trong lưới phải được bao phủ bởi tối đa một khối.

Barchin thắng trò chơi nếu, sau khi bất kỳ khối nào được đặt xuống, tồn tại một hình vuông \(2 \times 2\) gồm các ô được phủ bởi bốn viên gạch đen. Một cách hình thức, nếu các ô \((a, b)\), \((a + 1, b)\), \((a, b + 1)\), \((a + 1, b + 1)\) đều được phủ bởi các viên gạch đen với \(0 \le a < 2N - 1\) và \(0 \le b < 2M - 1\), thì Barchin thắng. Các chỉ số \(a\) và \(b\) không cần phải là số chẵn.

Charos thắng nếu cô ấy đặt tất cả \(N \cdot M\) khối mà không để Barchin thắng. Lưu ý rằng việc đặt \(N \cdot M\) khối sẽ phủ kín toàn bộ lưới.

Nhiệm vụ của bạn là cài đặt một chiến lược để Charos giành chiến thắng trong trò chơi. Có thể chứng minh rằng, với các điều kiện ràng buộc đã cho, Charos luôn có thể sắp xếp các khối sao cho đảm bảo chiến thắng, bất kể màu sắc của các khối mà cô ấy nhận được sau này là gì.

Chi tiết cài đặt

Bạn cần cài đặt hai hàm sau:

C++
void init(int N, int M)
  • N: bằng một nửa số hàng trong lưới.
  • M: bằng một nửa số cột trong lưới.
  • Hàm này được gọi đúng một lần cho mỗi trường hợp test, ngay khi bắt đầu thực thi chương trình của bạn.
C++
std::pair<int, int> receive_block(int TL, int TR, int BL, int BR)
  • TL, TR, BL, BR: lần lượt là màu của các viên gạch góc trên bên trái, góc trên bên phải, góc dưới bên trái và góc dưới bên phải của khối hiện tại, như được minh họa trong hình dưới đây. Mỗi giá trị có thể là \(0\) (trắng) hoặc \(1\) (đen).
  • Hàm này được gọi chính xác \(N \cdot M\) lần cho mỗi trường hợp test, sau lần gọi ban đầu đến hàm init.

Hàm này phải trả về một cặp số nguyên \((i, j)\), trong đó \(i\) là tọa độ hàng và \(j\) là tọa độ cột của ô mà viên gạch góc trên bên trái của khối này sẽ được đặt vào. Cả \(i\) và \(j\) đều phải là số chẵn, và vùng \(2 \times 2\) được khối này che phủ không được chồng chéo lên bất kỳ khối nào đã được đặt trước đó.

Nếu receive_block trả về một cặp số không thỏa mãn các yêu cầu này, hoặc nếu sau khi đặt khối, một ô vuông \(2 \times 2\) các ô bị che phủ hoàn toàn bởi các viên gạch màu đen, trình chấm điểm sẽ ngay lập tức kết thúc chương trình của bạn và kết quả cho trường hợp test sẽ là Output isn't correct.

Hành vi của hệ thống chấm điểm là không thích ứng. Điều này có nghĩa là chuỗi các khối mà Barchin giao cho Charos đã được xác định trước khi hàm init được gọi.

Các ràng buộc

Với mỗi khối, gọi \(S\) là số viên gạch màu đen trong bốn viên của khối đó. Tức là, \(S = TL + TR + BL + BR\).

  • \(1 \le N, M \le 100\).
  • \(0 \le S \le 3\) cho mỗi khối.

Các subtask

Subtask Điểm Các ràng buộc thêm
1 6 \(S = 1\) cho mỗi khối và \(N = 2\).
2 16 \(S = 3\) cho mỗi khối. \(N = M\), \(N\) là số chẵn, và mỗi cách trong bốn cách có thể tô màu khối xuất hiện chính xác \(\frac{N^2}{4}\) lần.
3 10 \(S = 1\) cho mỗi khối.
4 29 \(S \le 2\) cho mỗi khối.
5 39 Không có ràng buộc nào thêm.

Ví dụ

Xét một trò chơi với \(N = 1\) và \(M = 2\), do đó lưới có \(2\) hàng và \(4\) cột. Trình chấm đầu tiên gọi:

C++
init(1, 2)

Ban đầu, tất cả các ô đều trống. Lưới trông như sau:

Có \(N \cdot M = 2\) khối cần đặt. Giả sử Barchin đưa ra một khối gồm ba viên gạch đen và một viên gạch trắng ở góc trên bên phải. Trình chấm gọi:

C++
receive_block(1, 0, 1, 1)

Charos quyết định đặt khối này ở phía bên trái của lưới bằng cách trả về \((0, 0)\).

Lưới bây giờ trông như sau:

Sau đó, Barchin đưa ra một khối khác gồm ba viên gạch đen và một viên gạch trắng ở góc trên bên trái:

C++
receive_block(0, 1, 1, 1)

Ô duy nhất còn lại có hàng chẵn và cột chẵn có thể đóng vai trò là góc trên bên trái của khối \(2 \times 2\) là \((0, 2)\), vì vậy Charos trả về \((0, 2)\). Lưới cuối cùng sẽ như sau:

Không có ô vuông \(2 \times 2\) được phủ kín hoàn toàn bằng các viên gạch màu đen, vì vậy Charos đã đặt thành công tất cả các khối mà Barchin không bao giờ thắng. Charos thắng trò chơi.

Trình chấm mẫu

Định dạng dữ liệu vào:

N M
TL[0] TR[0] BL[0] BR[0]
TL[1] TR[1] BL[1] BR[1]
...
TL[NM-1] TR[NM-1] BL[NM-1] BR[NM-1]

Định dạng kết quả ra:

R[0] C[0]
R[1] C[1]
...
R[NM-1] C[NM-1]

Trong đó, \(R[k]\) và \(C[k]\) là cặp số nguyên được trả về bởi lần gọi thứ \(k\) của hàm receive_block.


Nguồn: Đề bài chính thức IOI 2026, bản tiếng Việt (VNM), ngày 1 — Tiling Game (tiling).