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

Trọng số

Trọng số
Nguồn: CLUE OJ – Chương 22: Quy hoạch động Độ khó: Khá QHĐ 3 trạng thái Chọn đoạn con

Đề bài

Trọng số của dãy số nguyên dương $A_1, A_2, \ldots, A_N$ được tính bằng:

$$\sum_{i=1}^{N-1} \left( A_{i+1} \bmod A_i \right)$$

Yêu cầu: chọn một dãy con liên tiếp của dãy đã cho rồi tăng tất cả các phần tử của dãy con đó lên $K$ đơn vị để trọng số của cả dãy đạt giá trị lớn nhất. Lưu ý: tính cả trường hợp để nguyên dãy ban đầu.

Dữ liệu: vào từ file văn bản WSEQ.INP gồm:

  • Dòng đầu là số nguyên dương $T$ là số test $(T \le 10)$;
  • Mỗi test gồm: dòng thứ nhất chứa 2 số nguyên dương $N, K$ $(N \le 10^5,\ K \le 10^9)$; dòng thứ hai chứa $N$ số nguyên dương $A_1, A_2, \ldots, A_N$ $(A_i \le 10^9)$.

Kết quả: ghi ra file văn bản WSEQ.OUT, với mỗi test một số nguyên duy nhất là trọng số lớn nhất tìm được.

Ràng buộc:

  • Có 1 điểm có $T = 1$ và $N \le 10^4$;
  • 2 điểm còn lại không còn ràng buộc gì thêm.
Ví dụ – input
2
3 1
5 4 3
2 3
7 11
Ví dụ – output
9
4

Giải thích ví dụ: test 1 – dãy $5, 4, 3$ có trọng số ban đầu $4 \bmod 5 + 3 \bmod 4 = 4 + 3 = 7$. Cộng $K = 1$ cho cả dãy được $6, 5, 4$: $5 \bmod 6 + 4 \bmod 5 = 5 + 4 = 9$ – lớn nhất (các đoạn khác cho $7, 8, 3, 4, 4$). Test 2 – dãy $7, 11$: để nguyên được $11 \bmod 7 = 4$; cộng 3 cho $[7]$ được $11 \bmod 10 = 1$, cho $[11]$ được $14 \bmod 7 = 0$, cho cả dãy được $14 \bmod 10 = 4$ → đáp án $4$ (để nguyên cũng là một lựa chọn).

Lời mở đầu

Bài này nhìn qua tưởng phải thử mọi đoạn con $[l, r]$ – có tới $\frac{N(N+1)}{2}$ đoạn. Mình sẽ đi từ cách thử thẳng $O(N^3)$, cải tiến thành $O(N^2)$ bằng cách cập nhật dần, rồi nhìn bài toán theo kiểu quy hoạch động trạng thái: mỗi phần tử hoặc ở trước đoạn, hoặc ở trong đoạn, hoặc ở sau đoạn – chỉ cần đi một lượt $O(N)$ là xong.

Lưu ý khi nộp bài: đề yêu cầu đọc/ghi file WSEQ.INP / WSEQ.OUT; code dùng if(fopen("WSEQ.INP", "r")) nên nộp trên CLUE OJ (đọc bàn phím) vẫn chạy đúng.
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 chính thức có gì?

Test$T$$N$$K$ lớn nhất$A_i$ lớn nhấtĐáp án lớn nhất
1–201/22 … 102020150
21–33110.000 … 10.00070.715.028999.967.792478.947.414.777
34–901100.000 … 100.000977.752.318999.997.79154.944.101.361.612
91–10010100.000 … 100.00081.585.678999.999.3795.393.165.623.556

Đề có 3 điểm: 1 điểm cho $T = 1$, $N \le 10^4$ và 2 điểm không ràng buộc thêm. Bộ test 100 test khớp đúng tỉ lệ đó: test 1–33 (33%) là subtask 1 – trong đó 20 test đầu rất nhỏ ($N \le 10$, số $\le 20$; test 1 chính là ví dụ với $T = 2$) và 13 test có $N = 10^4$; test 34–100 (67%) có $N = 10^5$, riêng 10 test cuối có $T = 10$ bộ dữ liệu.

Đáp án lên tới khoảng $5{,}5 \cdot 10^{13}$ (tổng của $10^5$ số dư, mỗi số tới $10^9$) nên bắt buộc dùng long long. Ngoài ra $A_i + K$ có thể tới $2 \cdot 10^9$ – vượt int.

PhầnĐiều kiệnTestCách 1 $O(N^3)$Cách 2 $O(N^2)$Cách 3 QHĐ $O(N)$
Subtask 1 – nhỏ$N \le 10$1–20quaquaqua
Subtask 1 – lớn$T = 1$, $N = 10^4$21–33quá thời gianquaqua
Subtask 2$N = 10^5$, $T \le 10$34–100quá thời gianquá thời gianqua

Cách 1Thử mọi đoạn, tính lại trọng số – $O(N^3)$

Cách thẳng thắn nhất: với mỗi đoạn $[l, r]$, tạo dãy mới $b$ (cộng $K$ cho các phần tử trong đoạn), tính trọng số của $b$ mất $O(N)$, lấy max. Khởi tạo đáp án bằng trọng số dãy ban đầu (trường hợp để nguyên).

C++▶ Chạy tay từng dòng codeCách 1: thử mọi đoạn [l, r], tạo dãy b rồi tính lại trọng số.
Dữ liệu mẫu:
Không gian$O(N)$
Thời gian$O(N^3)$ mỗi test

Kết quả chạy thật: bản C++ đúng 20/100 test – 20 test nhỏ ($N \le 10$), chậm nhất 0,004 giây; bản Python cũng qua 20 test này. Từ test 21 ($N = 10^4$) cần khoảng $\frac{N^3}{2} = 5 \cdot 10^{11}$ phép tính → quá thời gian.

Cách 2Mỗi cặp kề nhau chỉ có 4 khả năng – $O(N^2)$

Trọng số là tổng theo từng cặp kề nhau $(A_i, A_{i+1})$. Khi cộng $K$ cho một đoạn, mỗi cặp chỉ rơi vào 1 trong 4 trường hợp, ta tính sẵn cả 4 giá trị:

Trường hợp$A_i$ cộng $K$?$A_{i+1}$ cộng $K$?Đóng góp
w00[i]khôngkhông$A_{i+1} \bmod A_i$
w01[i]khôngcó (đoạn bắt đầu tại $i+1$)$(A_{i+1} + K) \bmod A_i$
w11[i]cócó (cả cặp trong đoạn)$(A_{i+1} + K) \bmod (A_i + K)$
w10[i]có (đoạn kết thúc tại $i$)không$A_{i+1} \bmod (A_i + K)$

Cố định $l$, cho $r$ tăng dần, trọng số thay đổi chỉ ở 2 cặp nên cập nhật $O(1)$: cặp $(r-1, r)$ chuyển từ w10 sang w11, cặp $(r, r+1)$ chuyển từ w00 sang w10.

C++▶ Chạy tay từng dòng codeCách 2: tính sẵn 4 giá trị cho mỗi cặp, cố định l và tăng dần r, cập nhật s trong O(1).
Dữ liệu mẫu:
Bảng dưới: mỗi cột là một cặp kề nhau, ô xanh cho biết với đoạn [l, r] hiện tại cặp đó đang dùng trường hợp nào.
Không gian$O(N)$
Thời gian$O(N^2)$ mỗi test

Kết quả chạy thật: bản C++ đúng 33/100 test – trọn subtask 1 (1 điểm), chậm nhất 0,08 giây ở $N = 10^4$. 67 test $N = 10^5$ cần $5 \cdot 10^9$ phép → quá thời gian (khoảng 2 giây mỗi test trước khi bị dừng). Bản Python chỉ qua 20 test nhỏ, các test $N = 10^4$ chạy khoảng 1,6 giây.

Ý tưởng QHĐMỗi phần tử ở 1 trong 3 trạng thái

Đi từ trái sang phải, mỗi phần tử $A_i$ thuộc đúng một trong ba "giai đoạn": 0 – đoạn chưa bắt đầu, 1 – đang ở trong đoạn (được cộng $K$), 2 – đoạn đã kết thúc. Các giai đoạn chỉ đi theo thứ tự $0 \to 1 \to 2$ (có thể bỏ qua 1 = để nguyên dãy). Đóng góp của cặp $(A_{i-1}, A_i)$ chỉ phụ thuộc vào trạng thái của 2 phần tử đó, nên:

Công thức QHĐ
$f_s[i]$ = trọng số lớn nhất của $A_1..A_i$ khi $A_i$ ở trạng thái $s$: $$f_0[i] = f_0[i-1] + w_{00}, \qquad f_1[i] = \max\big(f_0[i-1] + w_{01},\ f_1[i-1] + w_{11}\big), \qquad f_2[i] = \max\big(f_1[i-1] + w_{10},\ f_2[i-1] + w_{00}\big)$$ với $f_0[1] = f_1[1] = 0$, $f_2[1] = -\infty$ (phần tử đầu không thể "sau đoạn"). Đáp án $= \max(f_0[N], f_1[N], f_2[N])$.

Em nhập $K$ và dãy rồi bấm Tiếp. Mỗi bước tính cột $i$ của 3 hàng $f_0, f_1, f_2$; mũi tên nhỏ trong công thức cho biết ô lấy từ đâu. Bước cuối lần ngược ra đoạn được cộng $K$ (tô xanh):

cột đang tínhcột trước (được dùng tới)trạng thái tối ưu / đoạn được cộng K
Ví dụ nhanh:

Cách 3QHĐ 3 trạng thái – $O(N)$ (full điểm)

C++▶ Chạy tay từng dòng codeCách 3: QHĐ 3 trạng thái f0 / f1 / f2, mỗi phần tử tính trong O(1).
Dữ liệu mẫu:
  1. Dùng NEG = LLONG_MIN / 4 làm "âm vô cùng" cho $f_2[1]$, cộng thêm vài số dương cũng không bị tràn.
  2. Mọi phép tính dùng long long: $A_i + K$ tới $2 \cdot 10^9$, tổng tới $10^{14}$.
  3. Mỗi test chỉ một vòng lặp $N$ bước, 4 phép % mỗi bước – 10 test $N = 10^5$ chạy chưa tới 0,1 giây.
Không gian$O(N)$ (có thể $O(1)$)
Thời gian$O(N)$ mỗi test

Kết quả chạy thật: bản C++ đúng 100/100 test, chậm nhất 0,082 giây (test 94, $T = 10$, $N = 10^5$). Bản Python cũng 100/100, chậm nhất 0,58 giây.

Ghi chú của mình: C++: cách 1 ($O(N^3)$) 20/100, cách 2 ($O(N^2)$) 33/100 – đúng 1 điểm subtask 1, cách 3 (QHĐ 3 trạng thái $O(N)$) 100/100, chậm nhất 0,08 giây; Python cách 3 cũng 100/100 (0,58 giây). Nhớ long long cho cả $A_i + K$ lẫn tổng, và đừng quên trường hợp để nguyên dãy (trạng thái 0 đi hết dãy).
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 😇