Bài toán
Link đến bài toán: 2742. Painting the Walls
Phân tích
Đề bài đọc khá khó hiểu, /ᐠﹷ ‸ ﹷ ᐟ\ノ để giải thích cho dễ hiểu thì đề bài yêu cầu như thế này:
- Có 2 mảng
costvàtime, cùng độ dài,cost[i]là chi phí để sơn tường thứi,time[i]là thời gian để sơn tường thứi. paid paintersẽ sơn 1 bức tường với chi phí làcost[i]và thời gian làtime[i].free paintersẽ sơn 1 bức tường với chi phí là 0, thời gian là 1.- Mục tiêu là tìm cách sơn tường với chi phí nhỏ nhất.
Nhìn 1 cách đơn giản hóa bài toán, thì khi ta chọn một bức tường,
thực chất ta đang sơn time[i] + 1 (free painter sơn time[i] bức, paid painter sơn 1 bức)
bức tường với chi phí là cost[i].
Vậy, thực chất đây là một bài toán knapsack chọn hoặc không chọn. Ta có thể giải bài toán này bằng đệ quy và tối ưu bằng dynamic programming.
Công thức đệ quy:
f(i, remain) = min(f(i + 1, remain), f(i + 1, remain - 1 - time[i]) + cost[i])
Trong đó
f(i, remain)là chi phí nhỏ nhất để sơnremainbức tường từiđếnlen(cost) - 1f(i + 1, remain)có thể hiểu là ta không sơn tường thứif(i + 1, remain - 1 - time[i]) + cost[i]có thể hiểu là ta sơn tường thứi. Chi phí bằng chi phí để sơn tường thứicộng với chi phí để sơnremain - 1 - time[i]bức tường từi + 1đếnlen(cost) - 1.
Độ phức tạp là O(2n).
Giải thuật
Đệ quy
- Base case:
- remain <= 0 => ta đã sơn xong tất cả các tường => return 0
- i == len(cost) => không thể dùng thêm painter nào nữa => return một số lớn
- Nếu
remain > 0vài < len(cost):- Tính minCost nếu ta không sơn tường i:
minCost = f(i + 1, remain) - Tính minCost nếu ta sơn tường i:
minCost = min(minCost, f(i + 1, remain - 1 - time[i]) + cost[i])
- Tính minCost nếu ta không sơn tường i:
- Trả về
minCostnhỏ nhất.
func minCost(cost []int, time []int) int {
return f(0, len(cost), 0, cost, time)
}
func f(i, n, remain int, cost, time []int) int {
if remain <= 0 {
return 0
}
if i == n {
return 1e9
}
minCost := f(i + 1, n, remain, cost, time)
minCost = min(minCost, f(i + 1, n, remain - 1 - time[i], cost, time) + cost[i])
return minCost
}
Tối ưu: Nếu để ý kĩ, ta sẽ thấy nhiều trường hợp f(i, remain) được gọi lại nhiều lần. Ta có thể tối ưu bằng cách
dùng một mảng nhớ lại kết quả của các bài toán con đã được giải. Hoặc có thể dủng quy hoạch động? (=^OwO^=)
Dynamic programming
- Khởi tạo mảng
dpvớidp[i][j]là chi phí nhỏ nhất để sơnjbức tường từiđếnlen(cost) - 1, giá trị khởi tạo là0. - Đặt
dp[len(cost) - 1][0] = 1e9. (Hoặc 1 số lớn nào đó khác) - Với
itừlen(cost) - 1đến0:- Với
remaintừ1đếnlen(cost):- Tính minCostNotPaint khi ta không sơn tường
i:minCost = dp[i + 1][remain]. - Tính minCostPaint khi ta sơn tường
i:minCost = dp[i + 1][max(0, remain - 1 - time[i])] + cost[i]. dp[i][remain] = min(minCostNotPaint, minCostPaint).
- Tính minCostNotPaint khi ta không sơn tường
- Với
- Trả về
dp[0][len(cost)].
func minCost(cost []int, time []int) int {
n := len(cost)
dp := make([][]int, n + 1)
for i := 0; i <= n; i++ {
dp[i] = make([]int, n + 1)
dp[i][0] = 1e9
}
for i := n - 1; i >= 0; i-- {
for remain := 1; remain <= n; remain++ {
minCostNotPaint := dp[i + 1][remain]
minCostPaint := dp[i + 1][max(0, remain - 1 - time[i])] + cost[i]
dp[i][remain] = min(minCostNotPaint, minCostPaint)
}
}
return dp[0][n]
}
Độ phức tạp là O(n2). Độ phức tạp không gian là O(n2).
Tối ưu: Ta thấy rằng ta chỉ cần lưu lại 2 hàng của mảng dp thôi. Lúc này, độ phức tạp không gian sẽ là O(n).
Các bạn tự giải tiếp nhé, bài này được viết vào nửa đêm, tác giả thì đã buồn ngủ và lười viết tiếp. /ᐠ - ˕ -マ Ⳋ