数位 dp
简介
解决大区间 \([L, R]\) 中满足条件的元素的个数的统计问题。
通常使用记忆化搜索实现,尤其注意前导零与上界答案的情况。
例题
HDU2089 不要62
人机题。
注意特判 \(l = 0\) 的情况。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 20;
int l, r, len, a[N], dp[N][N];
inline int dfs(int pos, int limit, int last) {
if(! pos) return 1;
if(! limit && dp[pos][last] != -1) return dp[pos][last];
int res = 0, upper = limit ? a[pos] : 9;
for(int i = 0 ; i <= upper ; ++ i)
if(i != 4 && (last != 6 || i != 2)) res += dfs(pos - 1, limit && i == upper, i);
if(! limit) dp[pos][last] = res;
return res;
}
inline int solve(int x) {
if(x == -1) return 0;
len = 0;
while(x) a[++ len] = x % 10, x /= 10;
return dfs(len, true, 11);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
while(true) {
cin >> l >> r;
if(! l && ! r) return 0;
cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}
HDU5179 beautiful number
注意先判当前填的数是否为 \(0\)。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 20;
int T, l, r, len, a[N], dp[N][N];
inline int dfs(int pos, bool limit, bool lead0, int last) {
// cerr << "value:" << last << '\n';
if(! pos) return 1;
if(! limit && ! lead0 && dp[pos][last] != -1) return dp[pos][last];
int res = 0, upper = limit ? a[pos] : 9;
for(int i = 0 ; i <= upper ; ++ i) {
if(i && last % i == 0) res += dfs(pos - 1, limit && i == upper, false, i);
else if(! i && lead0) res += dfs(pos - 1, limit && i == upper, true, i);
}
if(! limit && ! lead0) dp[pos][last] = res;
return res;
}
inline int solve(int x) {
len = 0;
while(x) a[++ len] = x % 10, x /= 10;
return dfs(len, true, true, 0);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> T;
while(T --) {
cin >> l >> r;
cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}
P13085 [SCOI2009] windy 数(加强版)
记录前导零,当前是否有限制和上一位选的数字即可。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20;
int l, r, a[N], dp[N][N];
inline int dfs(int pos, int limit, int lead0, int last) {
if(! pos) return 1;
if(! limit && ! lead0 && dp[pos][last] != -1) return dp[pos][last];
int upper = limit ? a[pos] : 9, res = 0;
for(int i = 0 ; i <= upper ; ++ i) {
if(lead0 && ! i) res += dfs(pos - 1, limit && i == upper, true, 11);
else if(abs(i - last) >= 2) res += dfs(pos - 1, limit && i == upper, false, i);
}
if(! limit && ! lead0) dp[pos][last] = res;
return res;
}
inline int solve(int x) {
int len = 0;
while(x) a[++ len] = x % 10, x /= 10;
return dfs(len, true, true, 11);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> l >> r;
cout << solve(r) - solve(l - 1);
return 0;
}
P6218 [USACO06NOV] Round Numbers S
记录 \(0\) 出现的次数和 \(1\) 出现的次数即可。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 60;
int l, r, a[N], dp[N][N][N];
inline int dfs(int pos, bool limit, bool lead0, int cnt0, int cnt1) {
if(! pos) return cnt0 >= cnt1;
if(! limit && ! lead0 && dp[pos][cnt0][cnt1] != -1) return dp[pos][cnt0][cnt1];
int upper = limit ? a[pos] : 1, res = 0;
for(int i = 0 ; i <= upper ; ++ i) {
if(lead0 && ! i) res += dfs(pos - 1, limit && i == upper, true, 0, 0);
else res += dfs(pos - 1, limit && i == upper, false, cnt0 + (i == 0), cnt1 + (i == 1));
}
if(! limit && ! lead0) dp[pos][cnt0][cnt1] = res;
return res;
}
inline int solve(int x) {
int len = 0;
while(x) a[++ len] = x % 2, x /= 2;
return dfs(len, true, true, 0, 0);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> l >> r;
cout << solve(r) - solve(l - 1);
return 0;
}
CF628D Magic Numbers
由于 \(l, r\) 过大,不能用整型变量储存,因而统计答案的方式需要改变。
具体地,计算 solve(r) - solve(l)
且统计 \(l\) 处是否有贡献即可。
因为题目保证 \(l, r\) 数位相同,所以不需要记录前导零的信息。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e3 + 5;
const int mod = 1e9 + 7;
int m, d, len, a[N], dp[N][N];
string l, r;
inline bool check(string s) {
len = s.size();
s = " " + s;
int rem = 0;
for(int i = 1 ; i <= len ; ++ i) {
if(i & 1) {
if(s[i] - '0' == d) return false;
}
else if(s[i] - '0' != d) return false;
rem = (rem * 10 + (s[i] - '0')) % m;
}
return rem == 0;
}
inline int dfs(int pos, bool limit, int rem) {
// cerr << len << '\n';
if(! pos) return rem == 0;
if(! limit && dp[pos][rem] != -1) return dp[pos][rem];
bool odd = ((len - pos + 1) & 1);
int upper = limit ? a[pos] : 9, res = 0;
if(odd) {
for(int i = 0 ; i <= upper ; ++ i)
if(i != d) res = (res + dfs(pos - 1, limit && i == upper, (rem * 10 + i) % m)) % mod;
}
else if(d <= upper) res = (res + dfs(pos - 1, limit && d == upper, (rem * 10 + d) % m)) % mod;
if(! limit) dp[pos][rem] = res;
return res;
}
inline int solve(string s) {
len = s.size();
s = " " + s;
for(int i = 1 ; i <= len ; ++ i)
a[len - i + 1] = s[i] - '0';
return dfs(len, true, 0);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> m >> d >> l >> r;
cout << (solve(r) - solve(l) + check(l) + mod) % mod;
return 0;
}
P4124 [CQOI2016] 手机号码
复杂但是简单。
记录前 \(1~2\) 次出现的数字,当前是否合法和 \(4, 8\) 是否出现过即可。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20;
int l, r, len, a[N], dp[N][N][N][2][2][2];
inline int dfs(int pos, bool limit, bool have4, bool have8, bool ok, int last1, int last2) {
if(! pos) return ok && ! (have4 && have8);
if(! limit && dp[pos][last1][last2][have4][have8][ok] != -1) return dp[pos][last1][last2][have4][have8][ok];
int lower = (pos == len) ? 1 : 0;
int upper = limit ? a[pos] : 9, res = 0;
for(int i = lower ; i <= upper ; ++ i) {
bool flag;
if(i == last1 && i == last2) flag = true;
else flag = false;
if((i == 4 && have8) || (i == 8 && have4)) continue;
if(i == 4) res += dfs(pos - 1, limit && i == upper, true, false, flag | ok, last2, i);
else if(i == 8) res += dfs(pos - 1, limit && i == upper, false, true, flag | ok, last2, i);
else res += dfs(pos - 1, limit && i == upper, have4, have8, flag | ok, last2, i);
}
if(! limit) dp[pos][last1][last2][have4][have8][ok] = res;
return res;
}
inline int solve(int x) {
if(x <= 1e10) return 0;
len = 0;
while(x) a[++ len] = x % 10, x /= 10;
return dfs(len, true, false, false, false, 11, 11);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> l >> r;
cout << solve(r) - solve(l - 1);
return 0;
}
CF1036C Classy Numbers
注意数字是可以重复的,所以比较傻逼。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100;
int T, l, r, a[N], dp[N][N];
inline int dfs(int pos, bool limit, bool lead0, int cnt) {
if(! pos) return cnt <= 3;
if(! limit && ! lead0 && dp[pos][cnt] != -1) return dp[pos][cnt];
int res = 0, upper = limit ? a[pos] : 9;
for(int i = 0 ; i <= upper ; ++ i) {
if(! i && lead0) res += dfs(pos - 1, limit && i == upper, true, 0);
else if(! i) res += dfs(pos - 1, limit && i == upper, false, cnt);
else res += dfs(pos - 1, limit && i == upper, false, cnt + 1);
}
if(! limit && ! lead0) dp[pos][cnt] = res;
return res;
}
inline int solve(int x) {
int len = 0;
while(x) a[++ len] = x % 10, x /= 10;
return dfs(len, true, true, 0);
}
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp, -1, sizeof dp);
cin >> T;
while(T --) {
cin >> l >> r;
cout << solve(r) - solve(l - 1) << '\n';
}
return 0;
}