[Algo] 数位dp
1. 最大为N的数字组合
// 1. 最大为N的数字组合
// https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/
int func1(vector<int>& digit, int n, int len, int offset, int free, int fix) {
// free = 1: 当前位置可以任意选取数字
// fix = 0: 之前从未选取过数字(当前位置可以不选取数字)
if (len == 0) return fix;
int ans = 0;
if (fix == 0) ans += func1(digit, n, len - 1, offset / 10, 1, 0);
if (free == 1) ans += digit.size() * func1(digit, n, len - 1, offset / 10, 1, 1);
else {
int cur = (n / offset) % 10;
for (int number : digit) {
if (number < cur) ans += func1(digit, n, len - 1, offset / 10, 1, 1);
else if (number == cur) ans += func1(digit, n, len - 1, offset / 10, 0, 1);
else break;
}
}
return ans;
}
int func2(vector<int>& digit, int n, int len, int offset, vector<int>& help) {
if (len == 0) return 1;
int cur = (n / offset) % 10, ans = 0;
for (int number : digit) {
if (number < cur) ans += help[len - 1];
else if (number == cur) ans += func2(digit, n, len - 1, offset / 100, help);
else break;
}
return ans;
}
int atMostNGivenDigitSet(vector<string>& digits, int n) {
if (n == 0) return 0;
int num = digits.size();
vector<int> digit(num);
for (int i = 0; i < num; i++) digit[i] = digits[i][0] - '0';
int len = 1, offset = 1, tmp = n / 10;
while (tmp > 0) {
len++;
offset *= 10;
tmp /= 10;
}
// return func1(digit, n, len, offset, 0, 0);
vector<int> help(len);
help[0] = 1;
int sum = 0;
for (int i = 1; i < len; i++) {
help[i] = help[i - 1] * num;
sum += help[i];
}
return sum + func2(digit, n, len, offset, help);
}
2. 统计整数数目
// 2. 统计整数数目
// https://leetcode.cn/problems/count-of-integers/
int func3(vector<int>& num, int min_sum, int max_sum, int index, int sum, int free, vector<vector<vector<int>>>& dp) {
int n = num.size();
// <--------------------剪枝-------------------->
if (sum > max_sum) return 0;
if (sum + 9 * (n - index) < min_sum) return 0;
// <-------------------------------------------->
if (index == n) return 1;
if (dp[index][sum][free] != -1) return dp[index][sum][free];
int ans = 0;
if (free == 1) {
for (int i = 0; i <= 9; i++) ans = (ans + func3(num, min_sum, max_sum, index + 1, sum + i, 1, dp)) % MOD;
} else {
for (int i = 0; i < num[index]; i++) ans = (ans + func3(num, min_sum, max_sum, index + 1, sum + i, 1, dp)) % MOD;
ans = (ans + func3(num, min_sum, max_sum, index + 1, sum + num[index], 0, dp)) % MOD;
}
dp[index][sum][free] = ans;
return ans;
}
int check(vector<int>& num, int min_sum, int max_sum) {
int n = num.size(), sum = 0;
for (int i = 0; i < n; i++) sum += num[i];
if (sum >= min_sum && sum <= max_sum) return 1;
else return 0;
}
int count(string num1, string num2, int min_sum, int max_sum) {
int n = num1.size(), m = num2.size();
vector<int> v1(n), v2(m);
for (int i = 0; i < n; i++) v1[i] = num1[i] - '0';
for (int i = 0; i < m; i++) v2[i] = num2[i] - '0';
vector<vector<vector<int>>> dp1(n, vector<vector<int>>(max_sum + 1, vector<int>(2, -1)));
vector<vector<vector<int>>> dp2(m, vector<vector<int>>(max_sum + 1, vector<int>(2, -1)));
int cnt1 = func3(v1, min_sum, max_sum, 0, 0, 0, dp1);
int cnt2 = func3(v2, min_sum, max_sum, 0, 0, 0, dp2);
return ((cnt2 - cnt1 + MOD) % MOD + check(v1, min_sum, max_sum)) % MOD;
}
3. 统计特殊整数
// 3. 统计特殊整数
// https://leetcode.cn/problems/count-special-integers/
int func4(int n, int len, int offset, vector<int>& help, int status) {
if (len == 0) return 1;
int ans = 0, cur = n / offset % 10;
for (int i = 0; i < cur; i++) {
if (i == 0 && n / offset < 10) continue;
if ((status & (1 << i)) == 0) ans += help[len - 1];
}
if ((status & (1 << cur)) == 0) ans += func4(n, len - 1, offset / 10, help, status | (1 << cur));
return ans;
}
int countSpecialNumbers(int n) {
int len = 1, offset = 1, tmp = n / 10;
while (tmp > 0) {
len++;
offset *= 10;
tmp /= 10;
}
if (len == 1) return n;
int ans = 9;
for (int i = 2, j = 9, k = 9; i < len; i++, k--) {
j *= k;
ans += j;
}
vector<int> help(len);
help[0] = 1;
for (int i = 1; i < len; i++) help[i] = help[i - 1] * (10 + i - len);
return ans + func4(n, len, offset, help, 0);
}
4. 不含连续1的非负数
// 4. 不含连续1的非负数
// https://leetcode.cn/problems/non-negative-integers-without-consecutive-ones/description/
int func5(int n, vector<int> &cnt, int i, int p) {
if (i == -1) return 1;
int cur = (n >> i) & 1, ans = 0;
if (cur == 1) {
ans = cnt[i];
if (p == 1) return ans;
}
return ans + func5(n, cnt, i - 1, cur);
}
int findIntegers(int n) {
vector<int> cnt(32);
cnt[0] = 1;
cnt[1] = 2;
for (int i = 2; i < 32; i++) {
cnt[i] = cnt[i - 1] + cnt[i - 2];
}
return func5(n, cnt, 30, 0);
}
5. 数字1的个数
// 5. 数字1的个数
// https://leetcode.cn/problems/number-of-digit-one/
int countDigitOne(int n) {
int ans = 0;
for (long tmp = n, left, right = 1; tmp != 0; tmp /= 10, right *= 10) {
int cur = tmp % 10;
left = tmp / 10;
ans += left * right;
if (cur > 1) {
ans += right;
} else if (cur == 1) {
ans += n - right * tmp + 1;
}
}
return ans;
}