数位dp从入门到lv1
颇为特别的dp,其他dp都是O(1)查询,这个查询还要写一大堆
做法一:
先用常规dp的方法,预处理出dp[ i ][ j ],dp[ i ][ j ]表示的是第 i 位数是 j 时,符合条件的数的个数,例如 dp[7][2] 表示的是形如 2XXXXXX 的数一共有多少是符合条件的
这个dp是容易递推的
然后我们要算小于等于某个数N的情况
windy数 做法一
例: N=365734
把答案分成三部分
第一部分:位数和N一样, res1 = 1XXXXX + 2XXXXX = dp[ 6 ][ 1 ] + dp[ 6 ][ 2 ]
第二部分:位数<N的位数,res2 = 1XXXX + 2XXXX + ... + 9XXXX +1XXX + ... + 9XXX ... = ∑dp[ i ][ j ] (1 <= i < len(N) , 1 <= j <= 9)
第三部分:位数和N一样,且最高位和N一样,也就是3XXXXX (XXXXX <= 65734)的情况,
res3 = 30XXXX + 31XXXX + 35XXXX + 360XXX + 361XXX + ... + 364XXX = dp[ 5 ][ 0 ] + dp[ 5 ][ 1 ] + dp[ 5 ][ 5 ] + dp[ 4 ][ 0 ] +...+ dp[ 4 ][ 4 ];
注: 32XXXX,33XXXX,34XXXX不满足windy数的条件, 365XXX不满足条件,3650XX,3651XX....也就通通不满足条件了
#include<iostream>
#include<algorithm>
using namespace std;
int dp[13][10];
int ABS(int x) {
return x >= 0 ? x : -x;
}
void pre() {
for (int j = 0; j <= 9; j++) dp[1][j] = 1;
for (int i = 2; i <= 10; i++) {
for (int j = 0; j <= 9; j++) {
for (int k = 0; k <= 9; k++) {
if (ABS(j - k) >= 2) dp[i][j] += dp[i - 1][k];
}
}
}
}
int qiu(int x) {
if (x == 0) return 0;
int b[12], len = 0;
int res = 0;
while (x) {
b[++len] = x % 10;
x /= 10;
}
for (int j = 1; j < b[len]; j++) res += dp[len][j];//第一部分
for (int i = 1; i < len; i++) {//第二部分
for (int j = 1; j <= 9; j++) {
res += dp[i][j];
}
}
bool flag = true;
for (int i = len - 1; i; i--) {//第三部分
for (int j = 0; j < b[i]; j++) {
if (ABS(j - b[i+1]) >= 2) res += dp[i][j];
}
if (ABS(b[i + 1] - b[i]) < 2) {
flag = false;
break;
}
}
if (flag) res++;
return res;
}
int main()
{
pre();
int a, b;
cin >> a >> b;
cout << qiu(b) - qiu(a - 1) << endl;
return 0;
}
做法一总结 : 先预处理dp数组,然后把ans分为那三个部分,根据题意改写,第三部分受题意的影响最大,要灵活变化
做法二:
用记忆化搜索的方法,从最高位开始记忆化搜索,用dp[ pos ][ pre ][ lim ][ zero ]记忆化,pos表示是第几位,pre表示第pos + 1位填了哪个数,lim表示第pos位是否有上界限制,zero表示第pos位是否不能取0
windy数 做法二
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
int dp[12][10][2][2]; // dp[pos][pre][lim][zero]
int b[12],len;
int dfs(int pos, int pre, bool lim, bool zero) {
if (~dp[pos][pre][lim][zero]) return dp[pos][pre][lim][zero];
if (!pos) return dp[pos][pre][lim][zero] = 1;//pos==0说明搜到底了,在此题中,搜到底表示搜到了一个合法的数。
int res = 0;
int r = lim ? b[pos] : 9;
int l = zero ? 1 : 0;
for (int i = l; i <= r; i++) {
if (abs(i - pre) < 2) continue;
res += dfs(pos - 1, i, lim && i == r, 0);//不是最高位,可以取0
}
return dp[pos][pre][lim][zero] = res;//记忆化
}
int qiu(int x) {
if (x == 0) return 0;
int res = 0;
memset(dp, -1, sizeof(dp));//初始化
len = 0;
do {
b[++len] = x % 10;
x /= 10;
} while (x);
res += dfs(len, 11, 1, 1);//最高位有上界限制,且不能取0
for (int i = len - 1; i; i--) {
res += dfs(i, 11, 0, 1);//最高位无上界限制,不能取0
}
return res;
}
int main()
{
int a, b;
cin >> a >> b;
cout << qiu(b) - qiu(a - 1) << endl;
return 0;
}
记忆化搜索的做法在数位dp中很实用
洛谷P4124 [CQOI2016]手机号码
这题条件很多,但是用记忆化搜索开个多维数组,就好写了。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
long long dp[13][10][10][2][2][2][2][2];//dp[pos][pre][prpre][lim][zero][three][_4][_8]
int b[13];
int len = 0;
long long dfs(int pos, int pre,int prpre, bool lim, bool zero, bool three, bool _4, bool _8) {
if (_4 && _8) return 0;
if (~dp[pos][pre][prpre][lim][zero][three][_4][_8]) return dp[pos][pre][prpre][lim][zero][three][_4][_8];
if (!pos) return dp[pos][pre][prpre][lim][zero][three][_4][_8] = three;
int r = lim ? b[pos] : 9;
int l = zero ? 1 : 0;
long long res = 0;
for (int i = l; i <= r; i++) {
res += dfs(pos - 1, i, pre,lim && i == r, 0, three||(pre == prpre && i == pre), _4 || i == 4, _8 || i == 8);
}
return dp[pos][pre][prpre][lim][zero][three][_4][_8] = res;
}
long long qiu(long long x) {
memset(dp, -1, sizeof(dp));
if (x < 1e10) return 0;
len = 0;
do{
b[++len] = x % 10;
x /= 10;
} while (x);
long long res = 0;
res += dfs(len, 11, 12, 1, 1, 0, 0, 0);
return res;
}
int main()
{
long long a, b;
cin >> a >> b;
cout << qiu(b) - qiu(a - 1) << endl;
return 0;
}

浙公网安备 33010602011771号