1. Giới thiệu về DP theo chữ số
1.1 Giới thiệu về DP theo chữ số
Quy hoạch động số học (gọi tắt là “Digit DP”) là à một dạng bài toán quy hoạch động liên quan đến các chữ số, tức là thực hiện tính toán trên các chữ số. Ở đây, chữ số chỉ đến hàng đơn vị, hàng chục, hàng trăm, hàng nghìn, v.v.
DP theo chữ số thường được sử dụng để tính số lượng giá trị thỏa mãn điều kiện cụ thể trong một khoảng , hoặc để tìm số nhỏ thứ thỏa mãn điều kiện cụ thể.
DP theo chữ số thường có các đặc điểm sau:
- Các câu hỏi thường cung cấp một khoảng truy vấn (đôi khi chỉ cung cấp giới hạn trên) là giới hạn thống kê.
- Khoảng được cung cấp trong câu hỏi thường rất lớn (ví dụ: ), không thể giải quyết bằng phương pháp thô sơ.
- Các điều kiện giới hạn được cung cấp trong câu hỏi thường liên quan đến chữ số.
- Yêu cầu thống kê số lượng giá trị thỏa mãn điều kiện cụ thể hoặc tìm số nhỏ thứ thỏa mãn điều kiện cụ thể.
Câu hỏi yêu cầu đếm số lượng giá trị thỏa mãn điều kiện cụ thể trong một khoảng . Nếu chúng ta có thể tìm ra cách tính toán số lượng giá trị thỏa mãn điều kiện cụ thể trong khoảng tiền tố , chúng ta có thể sử dụng “ý tưởng tổng tiền tố” để tính toán số lượng giá trị thỏa mãn điều kiện cụ thể trong khoảng và khoảng , sau đó trừ hai giá trị này để có được kết quả mong muốn. Tức là: .
Sau khi sử dụng “ý tưởng tổng tiền tố”, vấn đề được chuyển đổi thành tính toán số lượng giá trị thỏa mãn điều kiện cụ thể trong khoảng .
Tiếp theo, chúng ta sẽ sử dụng ý tưởng cơ bản của DP theo chữ số.
Ý tưởng cơ bản của DP theo chữ số: Chia số trong khoảng thành các chữ số, sau đó xác định từng chữ số một.
Chúng ta chia các chữ số của số trong khoảng thành các chữ số riêng lẻ, sau đó xác định từng chữ số một trong số chúng để tính toán số lượng kịch bản khả thi trong khoảng.
DP theo chữ số có thể được triển khai bằng “đệ quy có nhớ (top-down)” hoặc “duyệt vòng lặp (bottom-up)“. Vì DP theo chữ số liên quan đến nhiều tham số cần xem xét, việc sử dụng “đệ quy có nhớ” dễ dàng hơn để truyền tham số, vì vậy ở đây chúng ta sử dụng phương pháp “đệ quy có nhớ” để triển khai.
Khi sử dụng “đệ quy có nhớ”, các tham số cần xem xét bao gồm:
- Vị trí chữ số hiện tại đang xét ().
- Tình trạng của chữ số trước (hoặc các chữ số trước đó), ví dụ: tổng của các chữ số trước (), số lần xuất hiện của một số cụ thể (), tập hợp các chữ số được chọn từ các chữ số trước đó (thường được sử dụng “nén trạng thái”, tức sử dụng một số nguyên nhị phân để biểu thị).
- Chữ số trước (hoặc các chữ số trước) có bằng các chữ số trước của giới hạn không (), được sử dụng để giới hạn phạm vi chữ số tìm kiếm lần này.
- Chữ số trước đã điền số (), nếu chữ số trước đã điền số, thì chữ số hiện tại có thể bắt đầu từ ; nếu chữ số trước chưa điền số, thì chữ số hiện tại có thể bỏ qua hoặc bắt đầu từ .
- Số nhỏ nhất () và số lớn nhất () mà chữ số hiện tại có thể chọn.
Code tương ứng như sau:
Tiếp theo, chúng ta sẽ hiểu cụ thể hơn về số học DP và cách giải quyết vấn đề thông qua một ví dụ đơn giản.
1.2 Đếm số nguyên đặc biệt
1.2.1 Liên kết đề bài
1.2.2 Tóm tắt bài toán
Mô tả: Cho một số nguyên dương .
Yêu cầu: Đếm số lượng số nguyên đặc biệt trong đoạn .
Giải thích:
- Số nguyên đặc biệt: Nếu mỗi chữ số của một số nguyên dương là duy nhất, thì số đó được gọi là số nguyên đặc biệt.
- .
Ví dụ:
- Ví dụ 1:
- Ví dụ 2:
1.2.3 Ý tưởng giải quyết
Ý tưởng 1: Quy hoạch động + DP theo chữ số
Chuyển đổi số thành chuỗi , định nghĩa hàm đệ quy def dfs(pos, state, isLimit, isNum):
để xây dựng số lượng các phương án hợp lệ cho tất cả các chữ số từ vị trí trở đi. Tiếp theo, thực hiện đệ quy theo các bước sau:
- Bắt đầu đệ quy từ
dfs(0, 0, True, False)
.dfs(0, 0, True, False)
có ý nghĩa:- Bắt đầu xây dựng từ vị trí .
- Ban đầu không sử dụng số nào (tức tập hợp số đã chọn trước đó là ).
- Ban đầu bị ràng buộc bởi chữ số cao nhất của .
- Ban đầu chưa điền số nào.
- Nếu gặp , tức là đã đến cuối chuỗi số, lúc này:
- Nếu , có nghĩa là phương án hiện tại đáp ứng yêu cầu, trả về số phương án là .
- Nếu , có nghĩa là phương án hiện tại không đáp ứng yêu cầu, trả về số phương án là .
- Nếu , định nghĩa số phương án và gán giá trị ban đầu là , tức là:
ans = 0
. - Nếu gặp , có nghĩa là các chữ số trước đó chưa được điền số, chữ số hiện tại có thể bỏ qua, trong trường hợp này số phương án bằng số phương án khi chưa điền số ở vị trí , tức là:
ans = dfs(i + 1, state, False, False)
. - Nếu , chữ số hiện tại phải điền một số. Lúc này:
- Dựa vào và để quyết định số nhỏ nhất có thể chọn cho chữ số hiện tại () và số lớn nhất có thể chọn (),
- Sau đó, duyệt qua các số có thể điền vào từ khoảng .
- Nếu số chưa được chọn trước đó () không nằm trong tập hợp các số đã chọn trước đó (), số phương án được cộng thêm số phương án sau khi chọn số cho chữ số hiện tại, tức là:
ans += dfs(pos + 1, state | (1 << x), isLimit and x == maxX, True)
.state | (1 << x)
biểu thị tập hợp các số đã chọn trước đó cộng với .isLimit and x == maxX
biểu thị chữ số bị ràng buộc bởi các chữ số trước và chữ số .- biểu thị chữ số đã chọn số.
Ý tưởng 1: Code
Ý tưởng 1: Phân tích độ phức tạp
- Độ phức tạp thời gian: , trong đó là số nguyên cho trước.
- Độ phức tạp không gian: .
2.2 Số lượng số 1
2.2.1 Liên kết đề bài
2.2.2 Tóm tắt đề bài
Mô tả: Cho một số nguyên .
Yêu cầu: Đếm số lượng số xuất hiện trong tất cả các số nguyên không âm nhỏ hơn hoặc bằng .
Giải thích:
- .
Ví dụ:
- Ví dụ 1:
- Ví dụ 2:
2.2.3 Ý tưởng giải quyết
Ý tưởng 1: Quy hoạch động + DP theo chữ số
Chuyển đổi số thành chuỗi , định nghĩa hàm đệ quy def dfs(pos, cnt, isLimit):
để xây dựng số lượng các số xuất hiện trong tất cả các chữ số từ vị trí trở đi. Tiếp theo, thực hiện đệ quy theo các bước sau:
- Bắt đầu đệ quy từ
dfs(0, 0, True)
.dfs(0, 0, True)
có ý nghĩa:- Bắt đầu xây dựng từ vị trí .
- Số lượng số xuất hiện trước đó là .
- Ban đầu bị ràng buộc bởi chữ số cao nhất của .
- Nếu gặp , tức là đã đến cuối chuỗi số, lúc này: trả về số lượng số đã đếm được .
- Nếu , định nghĩa số phương án và gán giá trị ban đầu là , tức là:
ans = 0
. - Bắt đầu tính toán số phương án:
- Vì không cần xét trường hợp số đứng đầu nên số nhỏ nhất có thể chọn cho chữ số hiện tại () là .
- Dựa vào để quyết định số lớn nhất có thể chọn cho chữ số hiện tại ().
- Sau đó, duyệt qua các số có thể điền vào từ khoảng .
- Số phương án được cộng thêm số phương án sau khi chọn số cho chữ số hiện tại, tức là:
ans += dfs(pos + 1, cnt + (d == 1), isLimit and d == maxX)
。cnt + (d == 1)
biểu thị số lượng số đã xuất hiện trước đó cộng thêm số lượng số xuất hiện ở chữ số hiện tại.isLimit and d == maxX
biểu thị chữ số bị ràng buộc bởi các chữ số trước và chữ số .
- Số phương án cuối cùng là
dfs(0, 0, True)
, trả về giá trị đó.
Ý tưởng 1: Code
Ý tưởng 1: Phân tích độ phức tạp
- Độ phức tạp thời gian: .
- Độ phức tạp không gian: .