牛客竞赛动态规划专题班数位dp例题

题单

1|0A - 0的个数


这题算是一个思维题。

我的做法是就是统计每一位的 0 有多少个。

例如4032

  • 个位的零有403
  • 十位的零有4010
  • 百位的零有3100+33种,即千位去[1,3]个位低两位取[00,99],或者千位取4低两位取[00,33]
  • 千位不能取零
#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; #define int i64 using vi = vector<i64>; using pii = pair<i64, i64>; const i64 mod = 1e9 + 7; const i64 inf = 1e18; int calc(int x) { if (x < 0) return 0; int ans = 1; for (int p = 1, y = 0; x > 0; p *= 10) { ans += (x / 10 - 1) * p; if (x % 10 > 0) ans += p; else ans += y + 1; y += (x % 10) * p, x /= 10; } return ans; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int l, r; while (true) { cin >> l >> r; if (l < 0 and r < 0) break; cout << calc(r) - calc(l - 1) << "\n"; } return 0; }

2|0B - 送分了QAQ


首先我们们要把数字分成若干位。

然后我们设计状态f[i][st]从最高位到第i位数对应情况为st,且剩下i1位任取,符合条件的数(包含38或包含4)的个数。

  • st=0表示前i位即没有4也没有38

  • st=1表示前i位即没有4也没有38,但第i3

  • st=2表示前i位包含438

然后我们在维护一个变量flag表示前i1位是否和原数相同,为什么要有这个呢?如果前i1位都相同,则当前位的取值只能为[0,a[i]],如果不是则可以取到[0,9]

#include<bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; #define int i64 using vi = vector<int>; vi a(20); vector f( 20 , vi(3 , -1)); int dp(int pos, int st, bool flag) { if (pos == 0) return st == 2; if (flag and f[pos][st] != -1) return f[pos][st]; int x = flag ? 9 : a[pos]; int ans = 0; for (int i = 0; i <= x; i++) { if (i == 4 or st == 2 or (st == 1 and i == 8)) ans += dp(pos - 1, 2, flag or i < x); else if (i == 3) ans += dp(pos - 1, 1, flag or i < x); else ans += dp(pos - 1, 0, flag or i < x); } if (flag) f[pos][st] = ans; return ans; } int calc(int x) { if( x <= 0 ) return 0; fill(a.begin(), a.end(), 0); int pos = 0; while (x) a[++pos] = x % 10, x /= 10; return dp(pos, 0, 0); } i32 main() { int l, r; while (true) { cin >> l >> r; if (l == 0 and r == 0) break; cout << calc(r) - calc(l - 1) << "\n"; } return 0; }

3|0C - 诡异数字


状态f[pos][x][num]表示从最高位到第pos位,且第pos位是x,并且x已经出现了num次有多少种数字。

然后计算出当前位可以枚举的最大值,然后枚举当前位。

如果枚举的当前位为x就检查是是否超出相关的限制。

然后递归求解就好。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; using vi = vector<i64>; using pii = pair<i64, i64>; #define int i64 const i64 inf = 1e18; const i64 mod = 20020219; vi limit, a(20); vector f(20, vector(10, vi(20, -1))); int dp(int pos, int x, int num, bool flag) { if (pos == 0) return 1; if (flag and f[pos][x][num] != -1) return f[pos][x][num]; int n = flag ? 9 : a[pos]; int ans = 0; for (int i = 0; i <= n; i++) { if (i == x) { if (num + 1 > limit[x]) continue; ans = (ans + dp(pos - 1, x, num + 1, flag or i < n)) % mod; } else { ans = (ans + dp(pos - 1, i, 1, flag or i < n)) % mod; } } if (flag) f[pos][x][num] = ans; return ans; } int calc(int x) { if (x < 0) return 0; a.clear(); int pos = 0; while (x) a[++pos] = x % 10, x /= 10; return dp(pos, 0, 0, false); } void solve() { f = vector(20, vector(10, vi(20, -1))); int l, r, n; cin >> l >> r >> n; limit = vi(10, inf); for (int x, len; n; n--) cin >> x >> len, limit[x] = min(limit[x], len); cout << (calc(r) - calc(l - 1) + mod ) % mod<< "\n"; return; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int T; for (cin >> T; T; T--) solve(); return 0; }

4|0D - 7的意志


首先序列中所有的数一定都是7的倍数,所以题目要求实际上就是求多少个数是七的倍数且数位之和也是七的倍数。

所以设状态为dp[pos][x][y]pos位摸7x,且数位和模7y复合条件的数有多少个。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; using vi = vector<i64>; using pii = pair<i64, i64>; #define int i64 vi a(20); vector f(20, vector(7, vi(7, -1))); int dp(int pos, int x, int y, bool flag) { if (pos == 0) return x == 0 and y == 0; if (flag and f[pos][x][y] != -1) return f[pos][x][y]; int ans = 0; int n = flag ? 9 : a[pos]; for (int i = 0; i <= n; i++) ans += dp(pos - 1, (x * 10 + i) % 7, (y + i) % 7, flag or i < n); if (flag) f[pos][x][y] = ans; return ans; } int calc(int x) { int pos = 0; while (x) a[++pos] = x % 10, x /= 10; return dp(pos, 0, 0, false); } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); for (int l, r; true;) { cin >> l >> r; if (l == 0 and r == 0) break; cout << calc(r) - calc(l - 1) << "\n"; } return 0; }

5|0E - Beautiful Numbers


设状态为f[pos][x][mod][sum]表示前pos位模数为x且位数模x的值为mod,位数之和为sum

然后只要枚举x112×9即可。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; using vi = vector<i64>; using pii = pair<i64, i64>; #define int i64 vi a(15); vector f(15, vector(120, vector(120, vi(120, -1)))); int dp(int pos, int x, int mod, int sum, bool flag) { if (pos == 0) return x == sum and mod % x == 0; if( flag and f[pos][x][mod][sum] != -1 ) return f[pos][x][mod][sum]; int ans = 0; int n = flag ? 9 : a[pos]; for (int i = 0, tmp; i <= n; i++) { tmp = (mod * 10 + i) % x; ans += dp(pos - 1, x, tmp, sum + i, flag or i < n); } if (flag) f[pos][x][mod][sum] = ans; return ans; } int calc(int x) { int pos = 0; while (x) a[++pos] = x % 10, x /= 10; int ans = 0; for (int i = 1; i <= pos * 9; i++) ans += dp(pos, i, 0, 0, false); return ans; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); int TC; cin >> TC; for (int n, i = 1; i <= TC; i++) { cin >> n; cout << "Case " << i << ": " << calc(n) << "\n"; } return 0; }

6|0


#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; using vi = vector<i64>; #define int i64 const int T = 2520; vi a(20), p, q(T + 1); vector<vector<vi>> f; int dp(int pos, int mod, int d, bool flag) { if (pos == 0) return mod % p[d] == 0; if (flag and f[pos][mod][d] != -1) return f[pos][mod][d]; int ans = 0, pd = p[d]; int n = flag ? 9 : a[pos]; for (int i = 0; i <= n; i++) ans += dp(pos - 1, (mod * 10 + i) % T, i ? q[lcm(pd, i)] : d, flag or i < n); if (flag) f[pos][mod][d] = ans; return ans; } int calc(int x) { int pos = 0; while (x) a[++pos] = x % 10, x /= 10; return dp(pos, 0, 0, false); } void init() { for (int i = 1; i <= T; i++) if (T % i == 0) q[i] = p.size(), p.push_back(i); f = vector(20, vector(2600, vi(50, -1))); return; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); init(); int TC; cin >> TC; for (int l, r; TC; TC--) { cin >> l >> r; cout << calc(r) - calc(l - 1) << "\n"; } return 0; }

7|0F - 美丽数


f[pos][mod][d]表示前pos位的数模2520,且前pos位的数的lcmd

为什么是2520,因为这是[0,9]任取的lcm的最大值。

看起来值域很大,但是实际上的数只有不到 50 个,离散化一下就好了。

#include <bits/stdc++.h> using namespace std; using i32 = int32_t; using i64 = int64_t; using vi = vector<i64>; #define int i64 const int T = 2520; vi a(20), p, q(T + 1); vector<vector<vi>> f; int dp(int pos, int mod, int d, bool flag) { if (pos == 0) return mod % p[d] == 0; if (flag and f[pos][mod][d] != -1) return f[pos][mod][d]; int ans = 0, pd = p[d]; int n = flag ? 9 : a[pos]; for (int i = 0; i <= n; i++) ans += dp(pos - 1, (mod * 10 + i) % T, i ? q[lcm(pd, i)] : d, flag or i < n); if (flag) f[pos][mod][d] = ans; return ans; } int calc(int x) { int pos = 0; while (x) a[++pos] = x % 10, x /= 10; return dp(pos, 0, 0, false); } void init() { for (int i = 1; i <= T; i++) if (T % i == 0) q[i] = p.size(), p.push_back(i); f = vector(20, vector(2600, vi(50, -1))); return; } i32 main() { ios::sync_with_stdio(false), cin.tie(nullptr); init(); int TC; cin >> TC; for (int l, r; TC; TC--) { cin >> l >> r; cout << calc(r) - calc(l - 1) << "\n"; } return 0; }

__EOF__

本文作者PHarr
本文链接https://www.cnblogs.com/PHarr/p/18103850.html
关于博主:前OIer,SMUer
版权声明CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
posted @   PHarr  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
历史上的今天:
2023-03-29 AtCoder Beginner Contest 245
点击右上角即可分享
微信分享提示