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ố.
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 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.
Nhận xét
Đăng nhận xét