4. Bài toán cái túi với số lượng hữu hạn

Bài toán cái túi với số lượng hữu hạn: Có loại hàng hóa và một cái túi có khối lượng tối đa là , hàng hóa loại thứ có khối lượng là , giá trị là , và số lượng là . Hỏi trong trường hợp tổng khối lượng không vượt quá giới hạn của cái túi, giá trị lớn nhất có thể đạt được là bao nhiêu?

4.1 Ý tưởng cơ bản của Bài toán cái túi với số lượng hữu hạn

Chúng ta có thể tham khảo cách định nghĩa trạng thái và ý tưởng cơ bản của vấn đề “Bài toán cái túi 0-1” để giải quyết vấn đề này. Đối với một cái túi có khối lượng , tối đa có thể chứa hàng hóa loại thứ . Vì vậy, chúng ta có thể thêm một vòng lặp để duyệt qua số lượng hàng hóa loại có thể chọn (từ đến ), từ đó chuyển đổi vấn đề “Bài toán cái túi với số lượng hữu hạn” thành “Bài toán cái túi 0-1”.

Ý tưởng 1: Quy hoạch động + ý tưởng cơ bản 2 chiều

1. Phân đoạn

Phân đoạn theo số thứ tự của loại hàng hóa và giới hạn khối lượng của cái túi hiện tại.

2. Định nghĩa trạng thái

Định nghĩa trạng thái như sau: đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt loại hàng hóa vào cái túi có khối lượng tối đa là .

Trạng thái là một mảng hai chiều, trong đó chiều thứ nhất đại diện cho “loại hàng hóa hiện đang xem xét”, chiều thứ hai đại diện cho “giới hạn khối lượng của cái túi hiện tại”, giá trị của mảng hai chiều đại diện cho “giá trị lớn nhất có thể đạt được”.

3. Công thức chuyển đổi trạng thái

.

4. Điều kiện ban đầu
  • Nếu giới hạn khối lượng của cái túi là , thì bất kể chọn hàng hóa nào, giá trị lớn nhất có thể đạt được sẽ luôn là , tức là .
  • Bất kể giới hạn khối lượng của cái túi là bao nhiêu, giá trị lớn nhất có thể đạt được bằng cách đặt loại hàng hóa vào cái túi sẽ luôn là , tức là .
5. Kết quả cuối cùng

Dựa trên trạng thái chúng ta đã định nghĩa trước đó, đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt loại hàng hóa vào cái túi có khối lượng tối đa là . Vì vậy, kết quả cuối cùng là , trong đó là số lượng loại hàng hóa, là giới hạn khối lượng của cái túi.

Ý tưởng 1: Code

class Solution:
    # Ý tưởng 1: Quy hoạch động + ý tưởng cơ bản 2 chiều
    def multiplePackMethod1(self, weight: [int], value: [int], count: [int], W: int):
        size = len(weight)
        dp = [[0 for _ in range(W + 1)] for _ in range(size + 1)]
        
        # Duyệt qua các loại hàng hóa
        for i in range(1, size + 1):
            # Duyệt qua giới hạn khối lượng của cái túi
            for w in range(W + 1):
                # Duyệt qua số lượng hàng hóa loại i - 1 có thể chọn
                for k in range(min(count[i - 1], w // weight[i - 1]) + 1):
                    # dp[i][w] lấy giá trị lớn nhất từ tất cả dp[i - 1][w - k * weight[i - 1] + k * value[i - 1]
                    dp[i][w] = max(dp[i][w], dp[i - 1][w - k * weight[i - 1]] + k * value[i - 1])
                    
        return dp[size][W]

Ý tưởng 1: Phân tích độ phức tạp

  • Độ phức tạp thời gian: , trong đó là số lượng loại hàng hóa, là giới hạn khối lượng của cái túi, là độ dài của mảng số lượng hàng hóa. Vì , nên độ phức tạp thời gian cũng có thể viết là .
  • Độ phức tạp không gian: .

4.2 Tối ưu bằng cách sử dụng mảng trượt cho Bài toán cái túi với số lượng hữu hạn

Trong vấn đề “Bài toán cái túi với số lượng vô hạn”, chúng ta đã thành công trong việc loại bỏ sự phụ thuộc vào số lượng hàng hóa bằng cách tối ưu “Công thức chuyển đổi trạng thái”, từ đó giảm chiều của độ phức tạp thời gian.

Trong vấn đề “Bài toán cái túi với số lượng hữu hạn”, khi chúng ta đang tính toán , chúng ta không thể biết được đã sử dụng bao nhiêu mục hàng hóa loại và liệu hàng hóa loại còn số lượng còn lại để chọn hay không. Điều này dẫn đến việc chúng ta không thể tối ưu “Công thức chuyển đổi trạng thái” để giảm độ phức tạp thời gian của vấn đề “Bài toán cái túi với số lượng hữu hạn”.

Tuy nhiên, chúng ta có thể tham khảo cách tối ưu “Bài toán cái túi với số lượng vô hạn” + “Mảng trượt” để giảm chiều của độ phức tạp không gian của thuật toán.

Ý tưởng 2: Quy hoạch động + Tối ưu bằng mảng trượt

1. Phân đoạn

Phân đoạn theo giới hạn khối lượng của cái túi hiện tại.

2. Định nghĩa trạng thái

Định nghĩa trạng thái như sau: đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt hàng hóa vào cái túi có khối lượng tối đa là .

3. Công thức chuyển đổi trạng thái

4. Điều kiện ban đầu
  • Bất kể giới hạn khối lượng của cái túi là bao nhiêu, nếu không chọn hàng hóa nào, giá trị lớn nhất có thể đạt được sẽ luôn là , tức là .
5. Kết quả cuối cùng

Dựa trên trạng thái chúng ta đã định nghĩa trước đó, đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt hàng hóa vào cái túi có khối lượng tối đa là . Vì vậy, kết quả cuối cùng là , trong đó là giới hạn khối lượng của cái túi.

Ý tưởng 2: Code

class Solution:
    # Ý tưởng 2: Quy hoạch động + Tối ưu bằng mảng trượt
    def multiplePackMethod2(self, weight: [int], value: [int], count: [int], W: int):
        size = len(weight)
        dp = [0 for _ in range(W + 1)]
        
        # Duyệt qua giới hạn khối lượng của cái túi
        for w in range(1, W + 1):
            # Duyệt qua các loại hàng hóa
            for i in range(1, size + 1):
                # Duyệt qua số lượng hàng hóa loại i - 1 có thể chọn
                for k in range(min(count[i - 1], w // weight[i - 1]) + 1):
                    # dp[w] lấy giá trị lớn nhất từ tất cả dp[w - k * weight[i - 1]] + k * value[i - 1]
                    dp[w] = max(dp[w], dp[w - k * weight[i - 1]] + k * value[i - 1])
                    
        return dp[W]

Ý tưởng 2: Phân tích độ phức tạp

  • Độ phức tạp thời gian: , trong đó là số lượng loại hàng hóa, là giới hạn khối lượng của cái túi, là độ dài của mảng số lượng hàng hóa. Vì , nên độ phức tạp thời gian cũng có thể viết là .
  • Độ phức tạp không gian: .

4.3 Tối ưu bằng cách sử dụng phân tích nhị phân cho Bài toán cái túi với số lượng hữu hạn

Trong “Ý tưởng 2”, chúng ta đã tối ưu không gian bằng cách sử dụng mảng trượt. Tuy nhiên, chúng ta vẫn không thể tối ưu thời gian của thuật toán.

Tuy nhiên, chúng ta có thể tối ưu thời gian bằng cách sử dụng phân tích nhị phân cho số lượng hàng hóa. Phương pháp này được gọi là “tối ưu nhị phân”.

Tối ưu nhị phân: Đơn giản là chia số lượng hàng hóa thành “các loại hàng hóa lớn được tạo thành từ hàng hóa đơn lẻ” và “số lượng hàng hóa còn lại không đủ mũ nguyên của số lượng hàng hóa, được tạo thành từ hàng hóa đơn lẻ”.

Ví dụ, nếu số lượng hàng hóa thứ , sử dụng phương pháp “tối ưu nhị phân”, chúng ta có thể chia thành tổng cộng hàng hóa. Điều này có nghĩa là chúng ta đã chia thành hàng hóa lớn:

  1. Hàng hóa lớn thứ gồm hàng hóa loại .
  2. Hàng hóa lớn thứ gồm hàng hóa loại .
  3. Hàng hóa lớn thứ gồm hàng hóa loại .
  4. Hàng hóa lớn thứ gồm hàng hóa loại .
  5. Hàng hóa lớn thứ gồm hàng hóa loại .

Với hàng hóa lớn này, chúng ta có thể tạo ra bất kỳ số lượng hàng hóa từ đến .

Bằng cách này, thay vì phải lặp lại lần (từ đến ) để xem xét số lượng hàng hóa thứ , chúng ta chỉ cần lặp lại lần.

Dưới đây là một số ví dụ khác:

  1. Nếu số lượng hàng hóa thứ , chúng ta có thể chia thành tổng cộng hàng hóa.
  2. Nếu số lượng hàng hóa thứ , chúng ta có thể chia thành tổng cộng hàng hóa.
  3. Nếu số lượng hàng hóa thứ , chúng ta có thể chia thành tổng cộng hàng hóa.

Sau khi tối ưu nhị phân, độ phức tạp thời gian của thuật toán giảm từ xuống còn .

Ý tưởng 3: Quy hoạch động + Tối ưu nhị phân

1. Phân đoạn

Phân đoạn theo giới hạn khối lượng của cái túi hiện tại.

2. Định nghĩa trạng thái

Định nghĩa trạng thái như sau: đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt hàng hóa vào cái túi có khối lượng tối đa là .

3. Công thức chuyển đổi trạng thái

4. Điều kiện ban đầu
  • Bất kể giới hạn khối lượng của cái túi là bao nhiêu, nếu không chọn hàng hóa nào, giá trị lớn nhất có thể đạt được sẽ luôn là , tức là .
5. Kết quả cuối cùng

Dựa trên trạng thái chúng ta đã định nghĩa trước đó, đại diện cho giá trị lớn nhất có thể đạt được bằng cách đặt hàng hóa vào cái túi có khối lượng tối đa là . Vì vậy, kết quả cuối cùng là , trong đó là giới hạn khối lượng của cái túi.

Ý tưởng 3: Mã

class Solution:
    # Ý tưởng 3: Quy hoạch động + Tối ưu nhị phân
    def multiplePackMethod3(self, weight: [int], value: [int], count: [int], W: int):
        weight_new, value_new = [], []
        
        # Tối ưu nhị phân
        for i in range(len(weight)):
            cnt = count[i]
            k = 1
            while k <= cnt:
                cnt -= k
                weight_new.append(weight[i] * k)
                value_new.append(value[i] * k)
                k *= 2
            if cnt > 0:
                weight_new.append(weight[i] * cnt)
                value_new.append(value[i] * cnt)
        
        dp = [0 for _ in range(W + 1)]
        size = len(weight_new)
        
        # Duyệt qua giới hạn khối lượng của cái túi
        for w in range(1, W + 1):
            # Duyệt qua các loại hàng hóa
            for i in range(1, size + 1):
                # Nếu khối lượng hàng hóa lớn hơn giới hạn khối lượng của cái túi, bỏ qua
                if weight_new[i - 1] > w:
                    continue
                # dp[w] lấy giá trị lớn nhất từ tất cả dp[w - weight_new[i - 1]] + value_new[i - 1]
                dp[w] = max(dp[w], dp[w - weight_new[i - 1]] + value_new[i - 1])
                    
        return dp[W]

Ý tưởng 3: Phân tích độ phức tạp

  • Độ phức tạp thời gian: , trong đó là giới hạn khối lượng của cái túi, là số lượng hàng hóa thứ .
  • Độ phức tạp không gian: .