Duyệt điểm
Đề bài
Xét lưới ô vuông tạo thành từ $n$ đoạn thẳng nằm ngang (các đoạn này được đánh số từ 1 đến $n$ từ trên xuống dưới) và $n$ đoạn thẳng dọc (cũng được đánh số từ 1 đến $n$) theo chiều từ trái qua phải. Giao của đoạn thẳng nằm ngang thứ $i$ và đoạn thẳng dọc thứ $j$ có toạ độ $(i, j)$.
Cho một tập $S$ gồm $n$ đoạn thẳng, đoạn thứ $i$ nằm trên đoạn thẳng thứ $i$ của lưới xác định bởi hai điểm $(i, l_i)$ và $(i, r_i)$.
Yêu cầu: Xác định độ dài của đường đi ngắn nhất dọc theo các cạnh của lưới từ điểm $(1, 1)$ đến điểm $(n, n)$ và thoả mãn các điều kiện:
- Chỉ đi sang phải, sang trái hoặc xuống dưới;
- Đi qua tất cả các điểm của các đoạn thẳng thuộc tập $S$ đã cho.
Ví dụ, với lưới $6 \times 6$ và 6 đoạn thẳng như sau (đoạn tô đỏ):
Khi đó, nếu xuất phát từ điểm $(1, 1)$ đến $(6, 6)$ cần đi một quãng đường ngắn nhất bằng 24.
Dữ liệu:
- Dòng đầu tiên chứa số nguyên dương $n$ $(1 \le n \le 200\,000)$.
- Dòng thứ $i$ trong $n$ dòng sau chứa hai số nguyên dương $l_i, r_i$ $(1 \le l_i \le r_i \le n,\ i = 1 \dots n)$.
Kết quả: một số nguyên duy nhất là độ dài của đường đi ngắn nhất tìm được.
Giới hạn thời gian: 1 giây, bộ nhớ: 256 MB. Dữ liệu nhập từ bàn phím, in ra màn hình.
6
2 6
3 4
1 3
1 2
3 6
4 524Lời mở đầu
Bài này nhìn thì giống bài đường đi trên lưới, nhưng điểm mấu chốt là: trên mỗi hàng, sau khi đi hết đoạn thẳng thì ta chỉ có thể đứng ở một trong hai đầu mút. Chỉ cần 2 trạng thái cho mỗi hàng là đủ để quy hoạch động – một ví dụ rất đẹp về việc chọn trạng thái thật gọn. Ngoài ra bài còn giấu một cái bẫy kinh điển: tràn số.
Bấm vào nút bên dưới để nộp bài và được hệ thống CLUE OJ chấm tự động.
Nộp bài trên CLUE OJLộ 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ì?
Đề không chia subtask, nhưng bộ 100 test chia rất rõ thành 2 nhóm:
| Nhóm | Test | $n$ | Đáp án | Số test đáp án vượt kiểu int | Độ dài đoạn trung bình |
|---|---|---|---|---|---|
| Nhóm 1 | 1–30 | 5 – 10 | 10 – 74 | 0 | 1,8 |
| Nhóm 2 | 31–100 | 200.000 – 200.000 | 20.964.905.692 – 21.077.639.492 | 70 | 81137,6 |
- Nhóm 1 (test 1–30): $n \le 10$ – lưới nhỏ xíu, vét cạn thoải mái. Có 74 đoạn chỉ là 1 điểm ($l_i = r_i$) và 3 đoạn phủ kín cả hàng.
- Nhóm 2 (test 31–100): cả 70 test đều có $n = 200\,000$ với các đoạn ngẫu nhiên khá dài, và đáp án của cả 70 test đều vượt quá $2^{31} - 1 \approx 2{,}1 \cdot 10^9$ (khoảng $2{,}1 \cdot 10^{10}$). Dùng kiểu
intlà mất trắng 70 điểm.
Kết quả chạy thật của các cách bên dưới theo từng nhóm:
| Nhóm | Test | Cách 1 vét cạn $2^n$ | Cách 2 QHĐ, kiểu int | Cách 2 QHĐ, long long |
|---|---|---|---|---|
| Nhóm 1 | 1–30 | 30/30 | 30/30 | 30/30 |
| Nhóm 2 | 31–100 | 0/70 | 0/70 | 70/70 |
Ý tưởngMỗi hàng chỉ có 2 cách kết thúc
Vì chỉ được đi trái, phải, xuống (không được đi lên), nên đường đi xử lý lần lượt từng hàng 1, 2, …, $n$; mỗi hàng đi xuống đúng 1 lần, tổng cộng $n - 1$ bước dọc. Ở hàng $i$, ta vào ở một cột $c$ nào đó và phải đi qua mọi điểm từ $l_i$ tới $r_i$. Muốn đi qua cả hai đầu mút thì:
- hoặc tới $r_i$ trước rồi quay về, kết thúc ở $l_i$: tốn $|c - r_i| + (r_i - l_i)$;
- hoặc tới $l_i$ trước rồi đi sang, kết thúc ở $r_i$: tốn $|c - l_i| + (r_i - l_i)$.
Kết thúc ở chỗ khác (giữa đoạn hoặc ngoài đoạn) không bao giờ có lợi: đứng ở đầu mút rồi đi tiếp sang hàng dưới cũng ngắn bằng hoặc hơn. Vậy sau mỗi hàng ta chỉ cần nhớ 2 con số:
$fR[i]$ = như trên nhưng đang đứng ở $(i, r_i)$.
Với $p_L = l_{i-1}$, $p_R = r_{i-1}$ là hai chỗ đứng của hàng trước (hàng "0" coi như đứng ở cột 1, $fL = fR = 0$): $$fL[i] = \min\big(fL[i-1] + |p_L - r_i|,\ fR[i-1] + |p_R - r_i|\big) + (r_i - l_i)$$ $$fR[i] = \min\big(fL[i-1] + |p_L - l_i|,\ fR[i-1] + |p_R - l_i|\big) + (r_i - l_i)$$ Đáp án $= \min\big(fL[n] + (n - l_n),\ fR[n] + (n - r_n)\big)$ $+ (n - 1)$.
Em bấm Tiếp để xem hai trạng thái được tính qua từng hàng: đường xanh dương là đường tốt nhất kết thúc ở đầu trái, đường cam là đường tốt nhất kết thúc ở đầu phải; cuối cùng đường xanh lá là đáp án. Em có thể sửa dữ liệu (mỗi cặp là $l_i\ r_i$):
Cách 1Vét cạn mọi cách kết thúc – nhóm 1
Mỗi hàng có 2 lựa chọn (kết thúc ở trái hay phải), nên có $2^n$ đường đi "hợp lý". Hàm đệ quy thu(i, c, len) thử cả hai lựa chọn ở hàng $i$ rồi đi xuống hàng $i + 1$, tới hàng cuối thì cộng thêm đoạn đi tới cột $n$.
daulà đầu mút đi tới trước,ketlà đầu mút kết thúc: đi từ $c$ tớidau, rồi đi hết đoạn ($r_i - l_i$) để tớiket.- Mỗi lần xuống hàng cộng thêm 1 (
len2 + 1). - Đã dùng
long longcho độ dài ngay từ đầu.
Kết quả chạy thật: Bản C++ đúng 30/100 test, sai ở test 31–100; test đúng chạy lâu nhất 0.01s. Bản Python đúng 30/100 test, sai ở test 31–100; test đúng chạy lâu nhất 0.03s. Ở 70 test lớn chương trình bị lỗi chạy (RE) ngay lập tức: đệ quy sâu 200 000 tầng làm tràn ngăn xếp (bản Python vượt giới hạn đệ quy) – mà kể cả không tràn thì $2^{200000}$ lời gọi cũng không bao giờ xong.
Cách 2QHĐ hai trạng thái – full điểm
Để ý cách 1 gọi thu(i, c, …) với rất nhiều giá trị len khác nhau nhưng chỉ có 2 giá trị $c$ ($l_{i-1}$ hoặc $r_{i-1}$). Với cùng chỗ đứng thì chỉ cần giữ len nhỏ nhất – đó chính là $fL$, $fR$ ở phần ý tưởng. Mỗi hàng tính 2 số từ 2 số của hàng trước, nên chỉ cần vài biến, không cần mảng:
- Phải tính
nL,nRvào biến tạm rồi mới gán chofL,fR: nếu gánfLtrước thì lúc tínhnRlại dùng nhầmfLcủa hàng mới. - Hàng "0" là điểm xuất phát $(1, 1)$:
pL = pR = 1,fL = fR = 0. - $n - 1$ bước đi xuống được cộng một lần ở cuối.
int. Mình đã thử thay hết long long trong code trên bằng int: bản int đúng 30/100 test, sai ở test 31–100; test đúng chạy lâu nhất 0.00s – sai đúng 70 test lớn vì đáp án thật cỡ $2{,}1 \cdot 10^{10}$.Kết quả chạy thật: Bản C++ đúng 100/100 test; test đúng chạy lâu nhất 0.03s. Bản Python đúng 100/100 test; test đúng chạy lâu nhất 0.35s. Full điểm.
Tổng kếtKết quả chạy thật trên bộ test
| Cách | Ngôn ngữ | Đúng | Lâu nhất | Ghi chú |
|---|---|---|---|---|
| 1. Vét cạn $2^n$ | C++ | 30/100 | 0.01s | Chỉ qua nhóm 1 ($n \le 10$) |
| Python | 30/100 | 0.03s | Chỉ qua nhóm 1 | |
2. QHĐ (biến int) | C++ | 30/100 | 0.00s | Đúng thuật toán nhưng tràn số ở cả 70 test lớn |
2. QHĐ (long long) | C++ | 100/100 | 0.03s | Full điểm |
| Python | 100/100 | 0.35s | Full điểm |
int.- Không biết học gì trước, học gì sau, học lan man nên kiến thức bị hổng.
- Bí bài không ai gỡ, ngồi cả buổi vẫn không ra rồi bỏ cuộc.
- Code chạy được nhưng không đủ điểm, không biết sai ở đâu, chậm ở đâu.
- Không có đề thi thử và người chấm, nên không biết mình đang ở mức nào.
- Dễ nản giữa chừng khi không có ai theo sát.
- Lộ trình 111 buổi video sắp sẵn từ dòng code đầu tiên đến đề HSG thật của các tỉnh.
- Thầy trực tiếp gỡ bí, chữa bài cho từng em qua nhóm Zalo riêng.
- Hệ thống chấm bài tự động 24/7 và thi thử HSG hằng tuần để biết chính xác mình đang ở đâu.
- Đóng học phí một lần, thầy kèm lâu dài: từ thi chuyên Tin đến HSG quốc gia và cả khi lên đại học.
Nhận xét
Đăng nhận xét