这篇博客主要是数位DP的模版
基本上数位DP的题目都是比较套路的:一般都是问一个区间中满足条件的数的个数
套路1:差分
- [L,R]中的答案 = [0,R]中答案 - [0,L]中答案 + chk(L)
套路2:按位DP(记忆化搜索实现),记录需要的前缀状态,特别的:
- f = 0/1 代表当前填的数字是否还和上界数字相同
- g = 0/1 代表当前是否还在填前导零
复杂度一般为 O(位数*前缀状态数*10)
【P2657 [SCOI2009]windy数】https://www.luogu.com.cn/problem/P2657
不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包括A和B,总共有多少个windy数?
#pragma GCC optimize(3,"Ofast","inline")//O3优化 #pragma GCC optimize(2)//O2优化 #include <algorithm> #include <string> #include <string.h> #include <vector> #include <map> #include <stack> #include <set> #include <queue> #include <math.h> #include <cstdio> #include <iomanip> #include <time.h> #include <bitset> #include <cmath> #include <sstream> #include <iostream> #include <cstring> #define LL long long #define ls nod<<1 #define rs (nod<<1)+1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define INF 0x3f3f3f3f #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) const double eps = 1e-10; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;} using namespace std; int mem[100][100][2][2]; int len; string L,R; string s; int dfs(int cur,int x,bool f,bool g) { if (cur == len) return 1; if (mem[cur][x][f][g] != -1) return mem[cur][x][f][g]; int ans = 0; int v = 9; if (f == 1) v = s[cur] - '0'; for (int i = 0;i <= v;i++) { if (g == 1) { if (i == 0) ans += dfs(cur+1,0,f&(i==v),1); else ans += dfs(cur+1,i,f&(i==v),0); } else if (abs(i-x) >= 2) ans += dfs(cur+1,i,f&(i==v),0); } return mem[cur][x][f][g] = ans; } int solve(string t) { s = t; len = s.length(); memset(mem,-1, sizeof(mem)); return dfs(0,0,1,1); } int check(string t) { for (int i = 1;i < t.length();i++) { if (abs(t[i]-t[i-1]) < 2) return 0; } return 1; } int main() { ios::sync_with_stdio(0); cin >> L >> R; LL ans = solve(R) - solve(L) + check(L); cout << ans << endl; return 0; }
【AcWing 310. 启示录】https://www.acwing.com/problem/content/312/
某数字的十进制表示中有三个连续的6,古代人也认为这是个魔鬼的数。求第k小的魔鬼数
只需要在之前那个模版上改变一下,记录一下前两位数以及该数是不是魔鬼数
然后我们进行二分,找到第一个数 R 内魔鬼数的个数 <= k-1
那么这个 R 就是符合题目要求的
#pragma GCC optimize(3,"Ofast","inline")//O3优化 #pragma GCC optimize(2)//O2优化 #include <algorithm> #include <string> #include <string.h> #include <vector> #include <map> #include <stack> #include <set> #include <queue> #include <math.h> #include <cstdio> #include <iomanip> #include <time.h> #include <bitset> #include <cmath> #include <sstream> #include <iostream> #include <cstring> #define LL long long #define ls nod<<1 #define rs (nod<<1)+1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define INF 0x3f3f3f3f #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) const double eps = 1e-10; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;} using namespace std; int mem[11][2][2][2][2]; int len,k; int b[11]; int dfs(int cur,bool p1,bool p2,bool m,bool f) { if (cur == len) return m == 1; if (mem[cur][p1][p2][m][f] != -1) return mem[cur][p1][p2][m][f]; int v = 9; if (f) v = b[cur]; LL ans = 0; for (int i = 0;i <= v;i++) { ans += dfs(cur+1,i == 6,p1,m || (p1 && p2 && (i == 6)),f && (i == v)); } return mem[cur][p1][p2][m][f] = ans; } bool check(LL x) { len = 0; while (x) { b[len++] = x % 10; x /= 10; } reverse(b,b+len); memset(mem,-1, sizeof(mem)); return dfs(0,0,0,0,1) <= k; } int main() { ios::sync_with_stdio(0); int T; cin >> T; while (T--) { cin >> k; k--; LL ans = INF; LL l = 0,r = 1e10; while (l < r) { LL mid = (l + r)/2; if (check(mid)) l = mid + 1; else { r = mid; } } cout << r << endl; } return 0; }
【AcWing 311. 月之谜】https://www.acwing.com/problem/content/313/
如果一个十进制数能够被它的各位数字之和整除,则称这个数为“月之数”。给定整数L和R,你需要计算闭区间[L,R]中有多少个“月之数”。
转换一下思路,我们去枚举这个十进制数的各数字的和这样就可以减少复杂度
#pragma GCC optimize(3,"Ofast","inline")//O3优化 #pragma GCC optimize(2)//O2优化 #include <algorithm> #include <string> #include <string.h> #include <vector> #include <map> #include <stack> #include <set> #include <queue> #include <math.h> #include <cstdio> #include <iomanip> #include <time.h> #include <bitset> #include <cmath> #include <sstream> #include <iostream> #include <cstring> #define LL long long #define ls nod<<1 #define rs (nod<<1)+1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define INF 0x3f3f3f3f #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) const double eps = 1e-10; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;} using namespace std; string L,R; int sum,len; string s; int mem[10][110][110][2]; int dfs(int cur,int sum1,int r,bool f) { if (cur == len) return sum1 == sum && r == 0; if (mem[cur][sum1][r][f] != -1) return mem[cur][sum1][r][f]; int v = 9; if (f) v = s[cur] - '0'; LL ans = 0; for (int i = 0;i <= v;i++) { ans += dfs(cur+1,sum1+i,(r*10+i)%sum,f && (i == v)); } return mem[cur][sum1][r][f] = ans; } int solve(string t) { s = t; len = s.length(); memset(mem,-1, sizeof(mem)); return dfs(0,0,0,1); } int check(string t) { len = t.length(); int sum1 = 0,x = 0; for (int i = 0;i < len;i++) { sum1 += (t[i] - '0'); x = x * 10 + (t[i]-'0'); } return sum == sum1 && x % sum == 0; } int main() { ios::sync_with_stdio(0); cin >> L >> R; LL ans = 0; for (sum = 1;sum <= 100;sum++) { ans += solve(R)-solve(L)+check(L); } cout << ans << endl; return 0; }
【AcWing 338. 计数问题】https://www.acwing.com/problem/content/340/
给定两个整数 a 和 b,求 a 和 b 之间的所有数字中0~9的出现次数。
对每个数位 0~9 我们都进行数位DP
#pragma GCC optimize(3,"Ofast","inline")//O3优化 #pragma GCC optimize(2)//O2优化 #include <algorithm> #include <string> #include <string.h> #include <vector> #include <map> #include <stack> #include <set> #include <queue> #include <math.h> #include <cstdio> #include <iomanip> #include <time.h> #include <bitset> #include <cmath> #include <sstream> #include <iostream> #include <cstring> #define LL long long #define ls nod<<1 #define rs (nod<<1)+1 #define pii pair<int,int> #define mp make_pair #define pb push_back #define INF 0x3f3f3f3f #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) const double eps = 1e-10; const int maxn = 2e5 + 10; const int mod = 1e9 + 7; int sgn(double a){return a < -eps ? -1 : a < eps ? 0 : 1;} using namespace std; string L,R; string s; int k,len; int mem[11][11][2][2]; int dfs(int cur,int cnt,bool g,bool f) { if (cur == len) return cnt; if (mem[cur][cnt][g][f] != -1) return mem[cur][cnt][g][f]; int v = 9; if (f) v = s[cur] - '0'; LL ans = 0; for (int i = 0;i <= v;i++) { if (g == 1) { if (i == 0) ans += dfs(cur+1,0,1,f && (i == v)); else ans += dfs(cur+1,cnt+(i==k),0,f && (i == v)); } else ans += dfs(cur+1,cnt+(i==k),0,f && (i == v)); } return mem[cur][cnt][g][f] = ans; } int solve(string t) { s = t; len = s.length(); memset(mem,-1, sizeof(mem)); return dfs(0,0,1,1); } int check(string t) { len = t.length(); int cnt = 0; for (int i = 0;i < len;i++) { if (t[i]-'0'==k) cnt++; } return cnt; } int main() { ios::sync_with_stdio(0); while (1) { cin >> L >> R; if (L == "0" && R == "0") break; if(R.length()<L.length() || R.length()==L.length() && R<L) swap(L,R); LL ans = 0; for (k = 0; k <= 9; k++) { ans = solve(R) - solve(L) + check(L); cout << ans << " "; } cout << endl; } return 0; }
博客参考:https://www.luogu.com.cn/blog/BeWild/post-shuo-wei-dp
来源:https://www.cnblogs.com/-Ackerman/p/12589079.html