牛客竞赛动态规划专题班数位dp例题
1|0A - 0的个数
这题算是一个思维题。
我的做法是就是统计每一位的 0 有多少个。
例如4032
- 个位的零有403种
- 十位的零有40∗10种
- 百位的零有3∗100+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,且剩下i−1位任取,符合条件的数(包含38或包含4)的个数。
-
st=0表示前i位即没有4也没有38
-
st=1表示前i位即没有4也没有38,但第i是3
-
st=2表示前i位包含4或38
然后我们在维护一个变量flag表示前i−1位是否和原数相同,为什么要有这个呢?如果前i−1位都相同,则当前位的取值只能为[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位摸7余x,且数位和模7余y复合条件的数有多少个。
#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
然后只要枚举x从1到12×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位的数的lcm为d。
为什么是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
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
本文链接:https://www.cnblogs.com/PHarr/p/18103850.html
关于博主:前OIer,SMUer
版权声明:CC BY-NC 4.0
声援博主:如果这篇文章对您有帮助,不妨给我点个赞
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2023-03-29 AtCoder Beginner Contest 245