M - windy数
这个数位dp还比较明显,而且也比较好写,不过还是被我写搓了,伤心ing
这个数位dp,就是dp这个状态需要好好想想,还有就是这个前导0的问题,这个就是需要认真看题目。
我开始直接定义的一维dp,dp[i]定义为i位个数满足条件的数有多少
但是这个状态定义的是不完整的,因为第i位可以是1,2,3,4.。。。
这样子就不对了,所以一维是不够的,需要二维。
dp[i][j]表示有i位数,且最高位是j的满足条件的数有多少。
这个状态定义完之后就是前导0的处理了,我一开始以为这个可以不用判断前导0,但是实际上这个是需要判断前导0的
因为如果有前导0,那么这个数就可以随意放,但是如果没有就必须和前面的数进行比较,
所以这个还是要进行前导0 的判断的,如果你不进行前导0的判断,那这个代码就会写的很麻烦,而且还不一定写对。
我们先假设pre==-2因为abs(0-(-2))>=2这个意思就是说如果有前导0 就是对的,可以自行理解一下代码。
#include <cstdio> #include <cstdlib> #include <cstring> #include <queue> #include <algorithm> #include <vector> #include <iostream> #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int maxn = 1010; typedef long long ll; ll dp[100][20];//前面i个数满足条件的数有多少 int a[maxn]; ll dfs(int pos,int pre,bool limit,bool lead) { if (pos == -1) return 1; if (!limit &&!lead &&dp[pos][pre] != -1) return dp[pos][pre]; int up = limit ? a[pos] : 9; ll ans = 0; for(int i=0;i<=up;i++) { if (abs(i - pre) < 2) continue; int p = i; if (lead&&i==0) p = -2; ans += dfs(pos - 1, p, limit && (i == up), p == -2); } if (!limit&&!lead) dp[pos][pre] = ans; return ans; } ll solve(int x) { int pos = 0; while(x) { a[pos++] = x % 10; x /= 10; } return dfs(pos - 1, -2, 1, 1); } int main() { int l, r; memset(dp, -1, sizeof(dp)); while(scanf("%d%d",&l,&r)!=EOF) { ll ans = solve(r); ll ana = solve(l - 1); cout << ans - ana<< endl; } return 0; }
这个数位dp还是挺难的,
本来数位dp的核心就在于对数的判断,如果你可以把你想要的数字用一个dp数组表达出来,那就基本上做完了。
比如说上面那个题目要求左右的数相差大于等于2,所以我的dp数组就是定义为dp[i][j]一个数以j开头一共有i位的数 满足条件的有多少个。
再比如一个比较经典的数位dp就是不要62,我的dp数组就是定义位枚举到第i位,第i位的前面一位是不是6,这个都可以唯一确定状态,而且还把我们对与数字的要求表达出来了。
再比如说这个题目Count The Bits 通过这个的学习,我们可以知道我们先对数字进行处理,然后就是再去定义dp比较好,最后我们要验证一下这个dp的状态是不是唯一。
这个题目的意思是给你两个数字,让你求这两个数字中满足题目要求的数字有多少个。
这个对数字的要求就是这个数字最后的结果要对这个数的每一位的非0的数进行整除。
这个和count the bits 其实有点像,不过没有他说的那么明白,这个我们应该也是要去找一个数,因为我们要对每一位非0的数都要是整数倍,
所以我们这个数的大小应是2~9都是整数倍,然后我们可以算出来2~9的最小公倍数是2520
但是不是说这样子就可以解决这个问题了,虽然我们可以把一个数缩小,这个有利于节约空间
但是我们怎么去判断一个数是不是他所有位数的整数倍呢?那么我们就把每一位数进行相乘,然后最后看这个数是不是满足要求,其实说到这里了,
就差不多会写了,就只剩下一个小问题了,就是dp数组,
这个时候dp数组的定义就是 dp[i][j][k] 表示到第 i 位 这个数字的每一位的数相乘为 j ,这个数字为 k
但是如果这样的话,这个数组就会变成 d[20][2520][2520] 很明显超内存了,所以不可以这么写。
但是如何可以让这个内存降下来呢? 根据别人打表发现这个每一位的乘积只有四十八种可能,
所以就可以通过对数的离散化来降低数组大小。
#include <cstdio> #include <cstdlib> #include <cstring> #include <queue> #include <algorithm> #include <vector> #include <iostream> #define inf 0x3f3f3f3f3f3f3f3f using namespace std; const int maxn = 1010; const int mod = 2520; typedef long long ll; ll dp[50][50][3000]; int f[3000]; int a[maxn]; ll gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); } ll lcm(int a, int b) { return a / gcd(a, b)*b; } ll dfs(int pos, int sum, int all, bool limit) { if (pos == -1) return sum % all == 0 ? 1 : 0; if (!limit&&dp[pos][f[all]][sum] != -1) return dp[pos][f[all]][sum]; int up = limit ? a[pos] : 9; ll ans = 0; for (int i = 0; i <= up; i++) { if (i == 0) ans += dfs(pos - 1, (sum * 10 % mod + i) % mod, all, limit && (i == up)); else ans += dfs(pos - 1, (sum * 10 % mod + i) % mod, lcm(all, i), limit && (i == up)); } if (!limit) dp[pos][f[all]][sum] = ans; return ans; } ll solve(ll x) { int pos = 0; while (x) { a[pos++] = x % 10; x /= 10; } return dfs(pos - 1, 0, 1, 1); } int main() { int n; cin >> n; int cnt = 0; memset(dp, -1, sizeof(dp)); for (int i = 1; i <= 2520; i++) if (2520 % i == 0) f[i] = ++cnt; while (n--) { ll a, b; scanf("%I64d%I64d", &a, &b); ll ans = solve(b) - solve(a - 1); cout << ans << endl; } return 0; }