这篇博客主要是数位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