题意:给定一个区间[n, m],求这个区间当中不含62和4的个数。例如62315,88914就是不符合条件的。
数位dp第一题,dp[i][j]表示以 j 开头的 i 位数满足条件的个数,先要预处理出来所有的情况,
下面是预处理的核心。其中k表示j后面的那一位。max_len是题目中给的n的最大位数,这里是7,第二层for是枚举第max_len - i位的值。
for (int i = 1; i <= max_len; i++) { for (int j = 0; j <= 9; j++) { for (int k = 0; k <= 9; k++) { if (j != 4 && !(j == 6 && k == 2)) dp[i][j] += dp[i - 1][k]; } } }预处理这些之后,可以对于输入的数字进行处理,还要用到一个函数,就是统计从0~n符合条件的数是多少。注意这是闭区间【0,n】。对于每一个数,统计每一位比他小的,从左到右统计,例如547这个数,首先从左边开始统计百位小于5符合条件的有dp[3][0], dp[3][1], dp[3][2], dp[3][3], 意思就是百位数取0的时候后面有多少,取1的时候有多少...知道取到比这个数小1,为什么没有dp[3][4],因为4不符合题目中的条件。同样,继续循环到十位,比4小的就是dp[2][3], dp[2][2], dp[2][1], dp[2][0], 继续往下走就是比7小的,dp[1][0], dp[1][1]...dp[1][6].
#include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> #include <algorithm> using namespace std; typedef long long ll; const int maxn = 11; int dp[maxn][maxn]; const int max_len = 7; void init() { dp[0][0] = 1; for (int i = 1; i <= max_len; i++) { for (int j = 0; j <= 9; j++) { for (int k = 0; k <= 9; k++) { if (j != 4 && !(j == 6 && k == 2)) dp[i][j] += dp[i - 1][k]; } } } } int digit[maxn]; bool check(int n) { for (int i = n; i > 0; i--) if (digit[i] == 4 || (digit[i] == 2 && digit[i + 1] == 6)) return false; return true; } int get_cnt(int n) { memset(digit, 0, sizeof(digit)); int cnt = 0; int ans = 0; while (n) { digit[++cnt] = n % 10; n /= 10; } if (check(cnt)) ans++; for (int i = cnt; i > 0; i--) { for (int j = 0; j < digit[i]; j++) { if (j != 4 && !(j == 2 && digit[i + 1] == 6)) ans += dp[i][j]; } if (digit[i] == 4 || (digit[i] == 2 && digit[i + 1] == 6)) break; } return ans; } int main() { init(); int n, m; while (~scanf("%d %d", &n, &m) && (n + m)) { int ans = get_cnt(m) - get_cnt(n - 1); printf("%d\n", ans); } return 0; }
还有一个技巧就是get_cnt(m)得到的是区间【0,m】之间的个数,所以【n, m】就是get_cnt(m) - get_cnt(n - 1),不过这里得处理一下函数判断当前这个数是不是符合条件。后来我看网上都直接写get_cnt(m+1) - get_cnt(n), 后来想想其实这样比我的写法要好一点,因为这样就可以不用判断当前这个值是否满足条件了。
时间: 2024-10-12 17:29:34