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
Thi thử miễn phí
🏆 Thi thử HSG Tin học tuần này trên ClueOJ
Mở công khai cho mọi người · đề do thầy Nguyễn Việt Nam Sơn biên soạn
Lịch thi, đề cũ & lời giải →

Số lượng tối thiểu

Số lượng tối thiểu
Nguồn: CLUE OJ – Chương 14: Cửa sổ trượt + Tham lam Độ khó: Dễ Tham lam Toán

Đề bài

Khu phố nhà Mai đã bị phong toả do mới phát sinh một số ca nhiễm Covid-19. Có $N$ người thuộc diện phải cách ly y tế. Để theo dõi mức độ phòng chống dịch của địa phương, cán bộ y tế đã thực hiện điều tra toàn bộ khu phố và cho kết quả là chỉ có đúng $a$ người trong số $N$ người này biết cách thực hiện tốt việc cách ly, những người không biết thì chắc chắn là sẽ không cách ly tốt. Do sơ suất nên cán bộ y tế chỉ ghi được danh sách $b$ người không thực hiện tốt cách ly, danh sách này có thể chưa đầy đủ nhưng cán bộ này đảm bảo rằng những người đã biết cách thực hiện mà không có tên trong danh sách là những người đã thực hiện tốt.

Yêu cầu: xác định số lượng tối thiểu và tối đa người đã thực hiện tốt việc cách ly.

Dữ liệu: gồm ba số nguyên $N$, $a$ và $b$ $(1 \le N \le 10^{18};\ 0 \le a, b \le N)$.

Kết quả: ghi ra hai số nguyên là số lượng tối thiểu và số lượng tối đa người thực hiện tốt việc cách ly.

Ví dụ – input
3 2 1
Ví dụ – output
1 2

Giải thích: có 3 người, 2 người biết cách (gọi là A, B), 1 người không biết (C), danh sách ghi 1 người cách ly không tốt. Nếu danh sách ghi tên C thì A, B đều cách ly tốt → 2 người. Nếu danh sách ghi tên A thì chỉ còn B cách ly tốt → 1 người. Vậy tối thiểu 1, tối đa 2.

Lời mở đầu

Bài ngắn gọn nhưng đọc đề hơi "xoắn". Mình sẽ chuyển câu chữ thành 4 nhóm người để thấy rõ đáp án, sau đó đi từ cách thử mọi khả năng (dễ nghĩ, nhưng chậm khi $N$ tới $10^{18}$) tới công thức $O(1)$ bằng lập luận tham lam. Chú ý $N$ tới $10^{18}$ nên mọi biến phải là long long.

Link nộp bài

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 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ì?

Bộ test có 100 test, đề không chia subtask; $N$ tăng dần theo thứ tự test, từ $1$ tới cỡ $8 \cdot 10^{17}$ (test 99). Mình chia thành 5 nhóm theo độ lớn của $N$ để xem cách 1 trụ được tới đâu:

TestNmin(a, b) lớn nhấtCách 1 C++Cách 2 C++
1 – 20$1$ … $99$$14$20/2020/20
21 – 40$549$ … $736.408$$86.622$20/2020/20
41 – 60$2,0·10^6$ … $8,8·10^9$$32.235$20/2020/20
61 – 80$3,1·10^10$ … $5,8·10^13$$9,4·10^9$19/2020/20
81 – 100$3,3·10^14$ … $7,9·10^17$$8,2·10^13$16/2020/20

Trường hợp đặc biệt: 49 test có đáp án tối thiểu bằng 0 (danh sách đủ dài để "nuốt" hết người biết cách, $b \ge a$), 7 test có tối thiểu = tối đa (ví dụ $a = 0$ hoặc $b = 0$ hoặc $b = N$), và 6 test có $a + b > N$ – lúc này danh sách bắt buộc phải chứa người biết cách nên tối đa nhỏ hơn $a$.

Ý tưởng4 nhóm người

Mỗi người có 2 thuộc tính: biết / không biết cách cách ly, và có / không có tên trong danh sách. Gọi $t$ là số người biết cách mà vẫn có tên trong danh sách. Khi đó:

Có tên trong danh sáchKhông có tênTổng
Biết cách$t$ (cách ly không tốt)$a - t$ (cách ly tốt)$a$
Không biết$b - t$ (không tốt)$N - a - b + t$ (không tốt)$N - a$
Tổng$b$$N - b$$N$

Người cách ly tốt chỉ có thể là ô "biết cách, không có tên": đúng $a - t$ người (người không biết thì chắc chắn không tốt, người có tên thì đã bị ghi là không tốt). Bốn ô trong bảng đều phải $\ge 0$:

Miền giá trị của t
$$\max(0,\ a + b - N) \;\le\; t \;\le\; \min(a, b)$$ Số người tốt $= a - t$ nhỏ nhất khi $t$ lớn nhất, lớn nhất khi $t$ nhỏ nhất.

Hiểu theo kiểu tham lam: muốn ít người tốt nhất thì "dồn" danh sách vào những người biết cách càng nhiều càng tốt ($t = \min(a, b)$); muốn nhiều người tốt nhất thì dồn danh sách vào những người không biết trước ($N - a$ chỗ), hết chỗ mới phải ghi lên người biết cách ($t = \max(0, a + b - N)$).

Em nhập $N, a, b$ nhỏ rồi bấm Tiếp để xem danh sách được "rải" lên từng người với mọi giá trị $t$:

Ví dụ nhanh:

Cách 1Thử mọi giá trị t – O(min(a, b))

Duyệt $t$ từ $0$ đến $\min(a, b)$; giá trị nào làm nhóm "không biết, có tên" vượt quá số người không biết ($b - t > N - a$) thì bỏ qua; còn lại cập nhật min/max của $a - t$.

C++▶ Chạy tay từng dòng codeCách 1: thử mọi số t người biết cách có tên trong danh sách – O(min(a, b)).
Dữ liệu mẫu:
  1. Điều kiện b - t > N - a: danh sách còn $b - t$ người phải là người không biết, mà chỉ có $N - a$ người không biết.
  2. Vòng lặp chạy $\min(a, b) + 1$ lần. Với $a, b$ cỡ $10^{12}$ trở lên thì không bao giờ chạy xong trong 1 giây.
Không gian$O(1)$
Thời gian$O(\min(a, b))$
Kết quả chạy thật cách 1: bản C++ đúng 95/100 test – nhanh hơn mình tưởng vì trong đa số test $a$ hoặc $b$ khá nhỏ (dù $N$ rất lớn); chỉ quá thời gian ở test 73, 89, 91, 98, 99 (cả $a$ và $b$ đều từ cỡ $10^{10}$ trở lên). Bản Python đúng 88/100 test (vòng lặp Python chậm hơn nên quá thời gian ở nhiều test hơn: 62, 73, 74, 76, 81, 84, 89, 90, 91, 96, 98, 99).

Cách 2Công thức tham lam – O(1)

Thay hai đầu mút của $t$ vào $a - t$:

Đáp án
Tối thiểu $= a - \min(a, b) = \max(0,\ a - b)$
Tối đa $= a - \max(0,\ a + b - N)$ $= \min(a,\ N - b)$
C++▶ Chạy tay từng dòng codeCách 2: công thức tham lam – O(1).
Dữ liệu mẫu:
  1. 0LL: số 0 kiểu long long – hàm max của C++ đòi 2 tham số cùng kiểu, viết max(0, a - b) sẽ báo lỗi biên dịch.
  2. Không có phép cộng $a + b$ nên không lo tràn số (dù $a + b \le 2 \cdot 10^{18}$ vẫn vừa long long).
Không gian$O(1)$
Thời gian$O(1)$
Kết quả chạy thật cách 2: bản C++ và bản Python đều đúng 100/100 test, mỗi test chạy trong tích tắc.

Tổng kếtSo sánh 2 cách

CáchĐộ phức tạpC++Python
1. Thử mọi t$O(\min(a, b))$95/10088/100
2. Công thức$O(1)$100/100100/100
Ghi chú của mình: tất cả code trong bài đều được chấm thật trên toàn bộ 100 test chính thức (giới hạn 1 giây, đo bằng thời gian CPU). Mẹo "kẻ bảng 2 × 2 rồi đặt ẩn cho một ô" dùng được cho rất nhiều bài đếm kiểu "có bao nhiêu người vừa A vừa B" – các bạn nhớ nhé.
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 😇
Tự học thuật toán: khó ở đâu?
Đọc xong một bài giải là bước đầu. Phần lớn học sinh tự học sẽ gặp những trở ngại sau:
  • 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.
Học cùng thầy Nam Sơn, các trở ngại đó được gỡ bỏ:
  • 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