Book Allocation Problem
Một kệ sách chứa các quyển sách theo thứ tự cố định, và pages[i] là số trang của quyển sách i. Hãy chia sách cho students học sinh: mỗi học sinh nhận một dãy sách liền kề không bị ngắt quãng, mỗi người nhận ít nhất một quyển và mỗi quyển được giao cho đúng một học sinh. Khối lượng sách của một học sinh là tổng số trang mà bạn ấy nhận.
Trả về giá trị nhỏ nhất có thể của khối lượng sách lớn nhất. Nếu số học sinh nhiều hơn số quyển sách, hãy trả về -1.
Hàm
- pagesinteger-array
- số trang của từng cuốn sách, theo thứ tự trên kệ
- studentsinteger
- số học sinh chia sẻ sách
- Trả vềinteger
- tải trọng lớn nhất nhỏ nhất có thể, hoặc -1 khi số học sinh nhiều hơn số sách
Ràng buộc
1 ≤ pages.length ≤ 50001 ≤ pages[i] ≤ 1051 ≤ students ≤ 104- Trả về
-1khistudents > pages.length.
Ví dụ
- Đầu vào
- pages = [25, 40, 15, 30, 50]students = 3
- Đầu ra
- 65
- Giải thích
- Phát
[25, 40],[15, 30]và[50]: tổng lần lượt là 65, 45 và 50. Giới hạn 64 không đáp ứng được: cách phát sẽ là[25],[40, 15],[30],[50], cho bốn học sinh.
- Đầu vào
- pages = [10, 20]students = 3
- Đầu ra
- -1
- Giải thích
- Không thể chia hai quyển sách cho ba học sinh, mỗi bạn một quyển, nên đáp án là
-1.
- Đầu vào
- pages = [7, 3, 9]students = 3
- Đầu ra
- 9
- Giải thích
- Ba quyển sách và ba học sinh, mỗi học sinh nhận một quyển. Quyển nặng nhất có 9 trang.
Câu hỏi mở rộng
Chứng minh rằng giá trị trung bình của số sách, làm tròn lên, cộng với cuốn sách dày nhất luôn là một giới hạn khả thi. Giới hạn trên này giúp tiết kiệm bao nhiêu lần kiểm tra khi xếp một kệ sách mỏng?
Gợi ý
Mở từng gợi ý một. Mỗi gợi ý tiết lộ thêm một chút.
Thay vì tìm cách cắt tối ưu, hãy chọn giới hạn số trang. Bạn cần bao nhiêu học sinh nếu không ai được mang nhiều hơn số trang đó?
Phát sách theo thứ tự trên kệ và chỉ chuyển sang học sinh tiếp theo khi cuốn sách tiếp theo vượt quá giới hạn. Giới hạn cao hơn không bao giờ cần thêm học sinh.
Trả về
-1khi số học sinh nhiều hơn số sách. Nếu không, hãy tìm kiếm nhị phân giới hạn từ giá trị lớn hơn giữa độ dày của cuốn sách dày nhất và phần chia trung bình, cho đến tổng số sách.
Lời giải
Có quá nhiều cách để chia kệ sách, nhưng kiểm tra một giới hạn trang sẽ không tốn nhiều công sức: phát sách theo thứ tự trên kệ, chuyển sang học sinh tiếp theo khi một cuốn sách vượt quá giới hạn, rồi đếm số học sinh. Giới hạn cao hơn không bao giờ cần nhiều học sinh hơn, vì vậy các giới hạn phù hợp tạo thành một dãy liên tiếp, và tìm kiếm nhị phân sẽ xác định điểm bắt đầu của dãy đó.
Tăng giới hạn trang từng trang một
Đúng, nhưng không chạy xong với các test lớn nhất
Ý tưởng
Bắt đầu từ L = max(pages), vì một bạn phải mang cuốn sách dày nhất, rồi tăng L từng trang một cho đến khi số lượng tính theo cách tham lam phù hợp với students. Mọi giới hạn thấp hơn đều không phù hợp, vậy nên giới hạn đầu tiên phù hợp chính là đáp án. Trong ví dụ đầu tiên, mọi giới hạn từ 50 đến 64 đều cần bốn học sinh, còn 65 cần ba học sinh: 16 lần kiểm tra.
Đáp án có thể bằng tổng số sách trên kệ: một học sinh và 5000 cuốn sách, mỗi cuốn có 10^5 trang, tức là khoảng 5 × 10^8 giới hạn, mỗi giới hạn cần duyệt qua 5000 cuốn sách. Trong cột thời gian, A là đáp án và m là số trang của cuốn sách dày nhất.
Thuật toán
- Nếu
students > pages.length, trả về-1. - Viết cách đếm tham lam cho giới hạn
L: bắt đầu với một học sinh và số trang được giao là 0; khiload + pages[i] > L, thêm một học sinh và đặt số trang được giao thànhpages[i], nếu không thì cộng số trang của cuốn sách vào số trang được giao. - Đặt
Lbằng số trang của cuốn sách dày nhất. - Trong khi số học sinh được đếm với
Lnhiều hơnstudents, cộng 1 vàoL. - Trả về
L.
def studentsNeeded(pages, limit):
count, load = 1, 0 # the first student starts with no pages
for p in pages:
if load + p > limit:
count += 1 # this book opens the next student's block
load = p
else:
load += p
return count
def allocateBooks(pages, students):
if students > len(pages):
return -1 # someone would get no book
limit = max(pages) # the thickest book has to fit somewhere
while studentsNeeded(pages, limit) > students:
limit += 1
return limitTìm kiếm nhị phân theo giới hạn trang
Ý tưởng
Số lượng học sinh tham lam là số học sinh ít nhất cần có để chia sách theo giới hạn L. So sánh từng học sinh với cách chia bất kỳ khác, mỗi học sinh trong cách chia tham lam chỉ dừng lại khi cuốn sách tiếp theo không vừa, vì vậy họ luôn đi được ít nhất đến vị trí đó trên kệ. Có ít học sinh hơn số bạn có cũng không sao: một học sinh đang giữ hai cuốn có thể đưa một cuốn cho học sinh chưa có cuốn nào, và không ai phải mang thêm sách.
Vì vậy, L phù hợp chính xác khi số lượng học sinh tham lam không vượt quá students. Bất kỳ giới hạn L nào phù hợp thì L+1 cũng phù hợp, nên tìm kiếm nhị phân sẽ tìm ra giới hạn nhỏ nhất phù hợp.
Khoảng tìm kiếm bắt đầu từ giá trị lớn hơn giữa max(pages) và phần chia trung bình, tức total / students được làm tròn lên, vì tải trọng lớn nhất ít nhất cũng bằng cả hai giá trị đó. Giới hạn trên là total. Trong ví dụ đầu tiên, khoảng đó là từ 54 đến 160, và thuật toán thử 107, 80, 67, 60, 64, 66 rồi 65.
Thuật toán
- Nếu
students > pages.length, trả về-1. - Cộng
totalvà tìm quyển sách dày nhất. Đặtlobằng giá trị lớn hơn giữa quyển sách dày nhất và(total + students - 1) / students, rồi đặthi = total. - Trong khi
lo < hi, lấymid = lo + (hi - lo) / 2. - Chạy phép đếm tham lam với
mid. Nếu kết quả không lớn hơnstudents, đặthi = mid; nếu không, đặtlo = mid + 1. - Trả về
lo.
def studentsNeeded(pages, limit):
count, load = 1, 0 # the first student starts with no pages
for p in pages:
if load + p > limit:
count += 1 # this book opens the next student's block
load = p
else:
load += p
return count
def allocateBooks(pages, students):
if students > len(pages):
return -1 # someone would get no book
total = sum(pages)
# no limit can be below the thickest book or below the average share
lo = max(max(pages), (total + students - 1) // students)
hi = total # one student could carry the whole shelf
while lo < hi:
mid = lo + (hi - lo) // 2
if studentsNeeded(pages, mid) <= students:
hi = mid # mid is enough, so the answer is mid or lower
else:
lo = mid + 1 # mid needs too many students
return lo
Lỗi thường gặp và trường hợp biên
- Bỏ qua bước kiểm tra
-1. Với[10, 20]và 3 học sinh, số học sinh tối thiểu theo thuật toán tham lam ở giới hạn 20 là 2, nên tìm kiếm trả về 20. - Bắt đầu khoảng tìm kiếm chỉ từ mức chia trung bình. Với
[1, 9]và 2 học sinh, mức trung bình là 5. Ở mức đó, thuật toán tham lam xếp cuốn sách 9 trang cho một học sinh riêng, đếm được 2 học sinh và trả về 5. - Kiểm tra xem số học sinh có đúng bằng
studentshay không. Với[5, 5, 5, 5, 5, 5]và 4 học sinh, đáp án là 10, tại đó thuật toán tham lam đếm được 3 học sinh. - Sắp xếp các cuốn sách. Với
[10, 1, 1, 10]và 2 học sinh, kết quả là 11, nhưng khi sắp xếp thành[1, 1, 10, 10], kết quả là 12.
Câu hỏi thường gặp3
Vấn đề phân bổ sách có giống với bài toán chia mảng để có tổng lớn nhất nhỏ nhất không?
Đúng. Cả hai đều chia một mảng thành một số cố định các khối liên tiếp không rỗng và tối thiểu hóa tổng lớn nhất của một khối. Cả hai đều được giải bằng cùng một phép tìm kiếm nhị phân và cùng một bước kiểm tra tham lam. Phiên bản sách bổ sung trường hợp -1.
Độ phức tạp thời gian của bài toán phân bổ sách là bao nhiêu?
Thời gian O(n log S) và bộ nhớ phụ O(1), trong đó n là số lượng sách và S là tổng số trang. Mỗi giới hạn được thử sẽ cần duyệt qua sách một lần, và quá trình tìm kiếm thử khoảng log2 S giới hạn: 29 với một kệ có 5 × 10^8 trang.
Khi nào không thể phân phát sách?
Chỉ khi số học sinh nhiều hơn số sách, vì mỗi học sinh cần ít nhất một quyển sách. Khi có đủ sách, bất kỳ nhóm nào gồm từ hai quyển sách trở lên cũng có thể đưa quyển cuối cùng cho một học sinh chưa có sách, cho đến khi mỗi học sinh đều có một quyển.
Bài tập tương tự
Các bài tập dùng cùng ý tưởng. Giải hai hoặc ba bài là bạn sẽ nhớ được mẫu này.
def allocateBooks(pages, students):
# Viết code tại đâyKiểm thử #1
Kiểm thử #2
Kiểm thử #3
Đối số
pages = [25, 40, 15, 30, 50] students = 3
Đầu ra mong đợi
65