Đề bài
- Một chuỗi được gọi là đối xứng nếu ghi viết xuôi hay ngược các kí tự của nó thì được hai xâu giống nhau.
- Một chuỗi được gọi là phi đối xứng nếu không tồn tại đoạn con liên tiếp nào đối xứng. Một số là phi đối xứng khi biểu diễn thập phân của nó là một chuỗi phi đối xứng.
- Đếm số lượng số phi đối xứng trên đoạn [a; b].
Input
- Một dòng duy nhất chứa hai số nguyên a, b (0 ≤ a ≤ b ≤ 10^18).
Output
- Số lượng số phi đối xứng trên đoạn [a; b].
Ví dụ 1
Input
C++
123 321
Output
C++
153
Ví dụ 2
Input
C++
11032505 10912606
Output
C++
-31995
Hướng dẫn giải
Ý tưởng
- Viết hàm
countPalinFree(n)
để tính số lượng số phi đối xứng ≤ n. - Kết quả bài toán:
result = countPalinFree(b) - countPalinFree(a - 1);
Nhận xét
- Một số chuỗi đối xứng đơn giản:
- Độ dài chẵn: 11, 22, 33, …
- Độ dài lẻ: 121, 232, 343, …
- Các chuỗi đối xứng dài hơn bắt buộc hình thành từ chuỗi độ dài 2 hoặc 3. Ví dụ: 123454321, 123321
→ Vậy để chuỗi không chứa chuỗi đối xứng → Chặn ngay từ khi gặp chuỗi đối xứng 2 hoặc 3.
Quy hoạch động
Đặt dp[pos][prev1][prev2][tight][leadingZero]
là số lượng số phi đối xứng ≤ N với:
pos
: Vị trí hiện tại đang xétprev1
,prev2
: Hai chữ số trước đó dùng để kiểm tra đối xứng Ví dụ: 123, digit tại pos = 3, prev1 = 2, prev2 = 1 Nếu digit == prev1 → chuỗi đối xứng 2. Ví dụ: 122 Nếu digit == prev2 → chuỗi đối xứng 3. Ví dụ: 121tight
: Cờ xác định số hiện tại có bị ràng buộc bởi sốn
hay khôngVới N = 321- Nếu số hiện tại đang xét là 1xy → x có thể là bất kì chữ số nào từ 0-9 → tight tại vị trí của x = false
- Nếu số hiện tại đang xét là 3xy → x phải ≤ 2 → tight = true
leadingZero
: Cờ xác định số có chữ số 0 vô nghĩa ở đầu hay không. Nếu leadingZero == true → Có chữ số 0 vô nghĩa → Chưa đủ số → prev1 và prev2 = -1
Cơ sở Quy hoạch động
Nếu đã xét hết các chữ số (pos == N.length()
) → Đây là một số phi đối xứng hợp lệ (Vì nếu không hợp lệ thì đã bị return ở những pos nhỏ hơn rồi. → return 1
.
Công thức truy hồi
- Xét các chữ số có thể thêm vào vị trí hiện tại:
- Nếu
tight == true
: Chữ số chỉ có thể từ0
đếnlimit
là chữ số tại vị trí hiện tại của n (limit = N[position]
) - Nếu
tight == false
: Chữ số có thể từ0
đến9
.
- Nếu
- Tính tight và leadingZero cho vị trí sắp tới
bool newTight = tight && digit == limit; bool nextLeadingZero = leadingZero && digit == 0;
- Với mỗi chữ số:
- Nếu
leadingZero == true
→ Bỏ qua kiểm tra đối xứng (chưa đủ số).result += dp(position + 1, -1, -1, newTight, true);
- Ngược lại, kiểm tra:
- Nếu chữ số tạo thành chuỗi đối xứng độ dài 2 (
digit == prev1
) hoặc độ dài 3 (digit == prev2
) →continue
.if (digit == previousDigit1 || digit == previousDigit2) { continue; }
- Nếu không, thêm chữ số vào số hiện tại và thực hiện tiếp logic.
result += dp(position + 1, digit, previousDigit1, newTight, false);
- Nếu chữ số tạo thành chuỗi đối xứng độ dài 2 (
- Nếu
Code
C++
#include <iostream>
typedef long long ll;
using namespace std;
ll memo[20][11][11][2][2];
string numberString;
ll dp(int position, int previousDigit1, int previousDigit2, bool tight, bool leadingZero) {
if (position == numberString.length()) {
return 1;
}
if (memo[position][previousDigit1 + 1][previousDigit2 + 1][tight][leadingZero] != -1) {
return memo[position][previousDigit1 + 1][previousDigit2 + 1][tight][leadingZero];
}
int limit = tight ? numberString[position] - '0' : 9;
ll result = 0;
for (int digit = 0; digit <= limit; ++digit) {
bool newTight = tight && digit == limit;
bool nextLeadingZero = leadingZero && digit == 0;
if (nextLeadingZero) {
result += dp(position + 1, -1, -1, newTight, true);
} else {
if (digit == previousDigit1 || digit == previousDigit2) {
continue;
}
result += dp(position + 1, digit, previousDigit1, newTight, false);
}
}
memo[position][previousDigit1 + 1][previousDigit2 + 1][tight][leadingZero] = result;
return result;
}
ll countPalinFree(ll n) {
numberString = to_string(n);
memset(memo, -1, sizeof(memo));
return dp(0, -1, -1, true, true);
}
int main() {
ll a, b;
cin >> a >> b;
ll result = countPalinFree(b) - countPalinFree(a - 1);
cout << result << endl;
return 0;
}