HDU 3555 Bomb (数位DP)
数位dp,主要用来解决统计满足某类特殊关系或有某些特点的区间内的数的个数,它是按位来进行计数统计的,可以保存子状态,速度较快。数位dp做多了后,套路基本上都差不多,关键把要保存的状态给抽象出来,保存下来。
简介:
顾名思义,所谓的数位DP就是按照数字的个,十,百,千……位数进行的DP。
数位DP的题目有着非常明显的性质:
询问[l,r]的区间内,有多少的数字满足某个性质
做法根据前缀和的思想,求出[0,l-1]和[0,r]中满足性质的数的个数,然后相减即可。
算法核心:
关于数位DP,貌似写法还是比较多的,有递归的,也有非递归的。
下面学习一下较好理解,可拓展性较高的递归写法。
LL dfs(int x,int pre,int bo,int limit);
一般需要以上参数(当然具体情况具体分析)。
- x表示当前的数位(一般都是从高位到低位)
- pre表示前一位的数字
- bo可以表示一些附加条件:是否有前项0,是否包含49,是否当前已经符合条件……
- limit这个很重要!它表示当前数位是否受到上一位的限制,比较抽象,举例说明
如果上限是135,前两位已经是1和3了,现在到了个位,个位只能是5以下的数字
注:如果当前受限,不能够记忆化,也不能返回记忆化的结果
为了避免多次调用时 每次上限不同 而导致的错乱影响
例题:HDU 3555
题意:a到b中有多少个数字包含49
思路:
dfs(x,pre,bo,limit)
表示当前位置,前一位的数字,当前是否已经包含49,以及是否有上界
当然,直接搜索肯定TLE,f[x][pre][bo]记忆化即可。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; const int N = 20; #define LL long long int dig[N]; LL f[N][10][2]; // 数位,前一位数,是否符合条件,是否符合限制 [从高位到低位处理] LL dfs(int x, int pre, int bo, int limit){ if (x < 0) return bo; // 已经枚举了最后一位 if (!limit && f[x][pre][bo] != -1) return f[x][pre][bo]; // 记忆化dp int last = limit ? dig[x] : 9; // 确定这一位的上限是多少 LL re = 0; for (int i = 0; i <= last; i++) re += dfs(x - 1, i, bo || (pre == 4 && i == 9), limit && (i == last)); if (!limit) f[x][pre][bo] = re; // 当前受限,不能够记忆化,也不能返回记忆化的结果 return re; } LL solve(LL n){ int len = 0; while (n){ dig[len++] = n % 10; n /= 10; } return dfs(len - 1, 0, 0, 1); } int main(){ memset(f, -1, sizeof(f)); int T; scanf("%d", &T); while (T--){ LL n; cin >> n; cout << solve(n) << endl; } return 0; }