Tuyển sinh 🎓 Mình nhận đào tạo lập trình giải thuật thi đấu: thi vào lớp 10 chuyên tin · thi học sinh giỏi tin học cấp tỉnh/thành · thi vào đội tuyển quốc gia · Olympic tin học sinh viên / ICPC · phỏng vấn thuật toán. Bấm vào đây để xem thông tin khóa học → Xem khóa học

Phần thưởng (Tin học trẻ toàn quốc 2025 – Vòng khu vực miền Bắc & miền Trung – Bảng B, C2)

Phần thưởng
Nguồn: Tin học trẻ 2025 Độ khó: Dễ Mảng Max/min tiền tố

Đề bài

Nguồn đề: Hội thi Tin học trẻ toàn quốc 2025 – Vòng khu vực miền Bắc & miền Trung – Bảng B và Bảng C2 (bài Phần thưởng). Đề bên dưới do mình viết lại bằng lời của mình cho dễ hiểu (giữ đúng nghĩa, đúng số liệu và ví dụ của đề gốc).

Alice tham gia một cuộc thi phát triển mô hình trí tuệ nhân tạo và được Ban tổ chức cho cơ hội nhận thưởng.

Có $n$ ô ($n \ge 2$) đánh số từ $1$ đến $n$, ô thứ $i$ ghi số nguyên $p_i$. Cho trước số nguyên không âm $d$. Alice được chọn hai ô $i$, $j$ sao cho hiệu hai số thứ tự lớn hơn $d$, tức là $|i - j| > d$. Gọi $t = p_i \cdot p_j$:

  • nếu $t > 0$ thì Alice nhận món quà trị giá $t$;
  • nếu $t \le 0$ thì Alice không nhận được gì (coi như giá trị $0$).

Yêu cầu: tìm giá trị lớn nhất của món quà Alice có thể nhận.

Dữ liệu vào:

  • Dòng 1: hai số nguyên $n, d$ ($0 \le d < n$).
  • Dòng 2: $n$ số nguyên $p_1, p_2, \dots, p_n$ ($|p_i| \le 10^9$).

Kết quả: một số nguyên là giá trị lớn nhất của món quà (in $0$ nếu Alice không thể nhận quà).

Subtask:

  • Subtask 1 (50%): $n \le 3 \cdot 10^3$;
  • Subtask 2 (50%): $n \le 3 \cdot 10^5$.

Giới hạn thời gian: 1 giây. Dữ liệu nhập từ bàn phím, in ra màn hình.

Ví dụ – input
5 1
1 -4 5 -4 1
Ví dụ – output
16

Giải thích: chọn ô 2 và ô 4 (cách nhau $4 - 2 = 2 > 1$), tích bằng $(-4) \cdot (-4) = 16$. Các cặp có tích dương khác như ô 1 và ô 3 ($1 \cdot 5 = 5$), ô 3 và ô 5 ($5 \cdot 1 = 5$) đều nhỏ hơn. Lưu ý cặp ô 2, ô 3 không được chọn vì chỉ cách nhau 1 (không lớn hơn $d = 1$).

Lời mở đầu

Bài "dễ mà dễ sai": ý tưởng chỉ là tìm tích lớn nhất của hai số, nhưng có ba cái bẫy nhỏ – số âm nhân số âm ra số dương, hai ô phải cách nhau hơn $d$, và tích tới $10^{18}$. Mình đi từ cách duyệt mọi cặp (50 điểm) tới cách $O(n)$ chỉ cần nhớ số lớn nhất và nhỏ nhất của phần đầu dãy.

Dành cho học viên · Ưu tiênNộp bài trên hệ thống CLUE OJ

Link này chỉ học viên đã tham gia tổ chức mới vào được. Học viên nên ưu tiên nộp ở đây vì sẽ được chấm nhanh hơn.

Nộp bài trên CLUE OJ

Lộ trình lời giải, bấm vào từng phần để nhảy tới:

Bộ testBộ test có gì?

Bộ test trên CLUE do mình tự sinh theo đúng giới hạn và tỉ lệ subtask của đề: 24 test (test 1 là ví dụ của đề), chia đều 12 test cho mỗi subtask.

NhómTest$n$ lớn nhấtCác giá trị $d$Đáp án = 0Toàn số âmKhông có số âmĐáp án = $10^{18}$
Subtask 11–123.0000, 1, 5, 8, 10, 100, 1.500, 2.998, 2.999test 3, 10test 2, 8test test 2, 4
Subtask 213–24300.0000, 1, 7, 100, 1.000, 99.999, 123.456, 150.000, 299.998test 15, 19, 21test 17test 18–19test 20
  • Có 5 test đáp án bằng 0 (test 3, 10, 15, 19, 21): ví dụ dãy toàn số 0, hoặc $d = n - 2$ nên chỉ có đúng một cặp hợp lệ mà tích lại âm. Lời giải nào khởi tạo best = LLONG_MIN rồi in thẳng ra sẽ sai các test này.
  • Các test "toàn số âm" bắt buộc phải xét số nhỏ nhất (âm nhất): tích hai số âm mới là số dương lớn.
  • Các test đáp án đúng bằng $10^{18}$ ($= 10^9 \cdot 10^9$ hoặc $(-10^9) \cdot (-10^9)$) bắt buộc dùng long long.
Đã nộp thử trên bộ chấm chính thức
Lời giải cuối cùng (cách 2) mình đã nộp lên hệ thống chấm chính thức của Hội thi (tinhoctre.vn) và được 100/100 điểm (AC).

Kết quả chạy thật các cách bên dưới theo từng subtask:

SubtaskĐiều kiệnTestCách 1
$O(n^2)$ C++
Cách 1
Python
Cách 2
$O(n)$ C++
Cách 2
Python
Subtask 1 (50%)$n \le 3 \cdot 10^3$1–1212/1212/1212/1212/12
Subtask 2 (50%)$n \le 3 \cdot 10^5$13–242/122/1212/1212/12

Ý tưởngCố định ô bên phải, ô bên trái chỉ cần là max hoặc min

Giả sử hai ô được chọn là $i < j$. Điều kiện $j - i > d$ tương đương $i \le j - d - 1$. Vậy khi cố định ô bên phải $j$, ô bên trái $i$ được chọn tuỳ ý trong đoạn đầu $1..j-d-1$.

Trong đoạn đầu đó, số nào nhân với $p_j$ cho tích lớn nhất?

  • Nếu $p_j > 0$: tích $p_i \cdot p_j$ càng lớn khi $p_i$ càng lớn → chọn max của đoạn đầu.
  • Nếu $p_j < 0$: tích càng lớn khi $p_i$ càng nhỏ (càng âm) → chọn min của đoạn đầu.
  • Nếu $p_j = 0$: tích luôn bằng 0.
Nhận xét
Không cần xét dấu $p_j$: cứ tính cả mx * p[j] và mn * p[j] rồi lấy cái lớn hơn. Khi $j$ tăng thêm 1, đoạn đầu chỉ thêm đúng một ô là ô $j - d - 1$, nên cập nhật mx, mn trong $O(1)$.

Em nhập dãy và $d$ rồi bấm Tiếp để xem ô $j$ chạy sang phải, vùng xanh là các ô $i$ hợp lệ (cách $j$ hơn $d$), vùng mờ là $d$ ô quá gần $j$:

Ví dụ nhanh:

Cách 1Duyệt mọi cặp – O(n²) (Subtask 1)

Với mỗi $i$, cho $j$ chạy từ $i + d + 1$ tới $n$ (để $j - i > d$), cập nhật best bằng tích $p_i \cdot p_j$.

C++▶ Chạy tay từng dòng codeCách 1: duyệt mọi cặp (i, j) với j − i > d.
Dữ liệu mẫu:
  1. best khởi tạo bằng 0 chứ không phải số rất nhỏ: món quà không dương thì coi như 0, và nếu không có cặp nào hợp lệ thì đáp án cũng là 0.
  2. Mảng p kiểu long long để phép nhân p[i] * p[j] (tới $10^{18}$) không bị tràn. Nếu khai báo int thì phép nhân hai int bị tràn trước khi gán vào best.
Không gian$O(n)$
Thời gian$O(n^2)$

Kết quả chạy thật: Bản C++ đúng 14/24 test, quá thời gian test 13–14, 16–20, 22–24 (các test đúng chạy lâu nhất 0.026s). Bản Python đúng 14/24 test, quá thời gian test 13–14, 16–20, 22–24 (các test đúng chạy lâu nhất 0.958s). Với $n = 3000$ có khoảng $4{,}5$ triệu cặp – C++ chạy tức thì, Python mất gần 1 giây (vừa kịp). Với $n = 3 \cdot 10^5$ có tới $4{,}5 \cdot 10^{10}$ cặp → quá thời gian; hai test 15 và 21 của subtask 2 vẫn đúng vì $d$ rất lớn nên chỉ có vài cặp hợp lệ. Vẫn là 50 điểm.

Cách 2Max/min của đoạn đầu – O(n) (full)

Cho $j$ chạy từ $d + 2$ tới $n$ (ô bên phải nhỏ nhất có thể là $d + 2$, ứng với $i = 1$). Mỗi lần $j$ tăng, ô $j - d - 1$ vừa "đủ xa" nên được thêm vào đoạn đầu, cập nhật mx, mn; rồi thử hai tích mx * p[j], mn * p[j].

C++▶ Chạy tay từng dòng codeCách 2: cố định ô phải j, giữ max/min của các ô bên trái đủ xa.
Dữ liệu mẫu:
  1. Không cần xử lý riêng trường hợp $d = n - 1$ (không có cặp nào): vòng lặp không chạy lần nào, in best = 0.
  2. mx, mn được khởi tạo là LLONG_MIN, LLONG_MAX nhưng trước mỗi phép nhân chúng đã được cập nhật bằng một số thật trong dãy, nên phép nhân không bao giờ dùng giá trị khởi tạo (không tràn số).
  3. Thử với ví dụ ($d = 1$): $j = 3$: đoạn đầu $\{1\}$, tích $1 \cdot 5 = 5$; $j = 4$: đoạn đầu $\{1, -4\}$, $mn \cdot p_4 = (-4)(-4) = 16$; $j = 5$: đoạn đầu $\{1, -4, 5\}$, $mx \cdot p_5 = 5$. Đáp án 16 ✓.
Không gian$O(n)$
Thời gian$O(n)$

Kết quả chạy thật: Bản C++ đúng 24/24 test, chạy lâu nhất 0.034s. Bản Python đúng 24/24 test, chạy lâu nhất 0.302s. Full điểm.

Tổng kếtKết quả chạy thật trên bộ test

CáchNgôn ngữĐúngLâu nhất
(test đúng)
Điểm theo subtask
1. Duyệt mọi cặp $O(n^2)$C++14/240.026s50%
Python14/240.958s50%
2. Max/min tiền tố $O(n)$C++24/240.034s100%
Python24/240.302s100%
Ghi chú của mình: thời gian là thời gian CPU đo trên máy mình (giới hạn 1 giây). Kỹ thuật "cố định một đầu, đầu còn lại chỉ cần max/min của tiền tố" gặp rất nhiều (bài mua bán cổ phiếu, chênh lệch lớn nhất...). Điểm khác ở bài này là có số âm, nên phải giữ cả max lẫn min; và đừng quên đáp án nhỏ nhất là 0, không phải số âm.
Rất vui khi anh em nào đã đọc đến những dòng cuối cùng này. Anh em đọc chắc cũng mỏi mắt luôn chứ 😅 vậy anh em nghĩ người viết ra hết đống ở trên còn thế nào nữa 🤪. Vậy nên đừng tiếc gì cho mình một lời bình luận hoặc chia sẻ bài viết để động viên mình nè. Chúc anh em học tốt và gặt hái nhiều thành công 😇

Nhận xét