数位dp问题题型往往是这样的:
给定一个区间[L,R],求这个区间中满足“某种条件”的数的总数。
题目:求区间[L,R]范围内有多少带3的数,所谓带3的数就是这个数十进制表示中存在至少一位3
比如3,,123,3333,都是带3的数,如12,456,1000都是不带3的数
输入:一行包含两个整数L,R(1<=L<R<1e12)
输出:输出一个整数表示区间[L,R]范围内带3的个数
例子:输入 100 2000
输出:19
在数位dp中由于数字过大,基本都要使用记忆化搜索,我们通过分析可以知道,在dfs中很多的数和前面dfs算过的数是一样的,只要不在限制位,我们把它存起来直接用就行了
#include<iostream>
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define maxn 15
int num;
int* a = new int[maxn];
int f[15];
//int a[maxn];
int b[maxn];//b保存第p为存的是那个数
int ten[maxn];
int L, R;
int t;
int dfs(int p, bool limit) {//p表示在第p位,limite表示此时是否处于限制位
if (p < 0) {
//for (int i = 2; i >= 0; i--)cout << b[i];//无限递归,记得加结束return
//cout << endl;
return 0;//搜索结束,返回
}
if (!limit && f[p] != -1) {//记忆化搜索,不处于限制位,并且f[p]被算过了
return f[p];
}
int up = limit ? a[p] : 9;//判断是否处于限制位,如果是就只能取到a[p]为,否则最高位能取到9
int ans = 0;
for (int i = 0; i <= up; i++) {
//b[p] = i;
if (i == 3) {
if (limit && i == up) {
ans += 1;
for (int j = p - 1; j >= 0; j--)//处于限制条件就把限制数下面全算上
ans += a[j] * ten[j];
}
else//如果不处于限制条件直接加上10的p次方
ans += ten[p];
}
else ans += dfs(p - 1, limit && i == up);//这里填a[p]可以填up也行,在处于限制的时候up等于a[p]
}
if (!limit)//记忆化搜索,如果没有处于限制条件就可以直接那算过一次的数直接用,能节省很多时间
f[p] = ans;
return ans;
}
int handle(int num) {
int p = 0;
while (num) {//把num中的每一位放入数组
a[p++] = num % 10;
num /= 10;
}
//说明a数组写进去了,但是读取无效数据是什么意思勒,之前好像不是这样的,解决办法,动态创建数组
/*for (int i = 0; i < p; i++) {
cout << a[i];
}*/
return dfs(p - 1, true);//对应的最高位为p-1位,为True表示没有处于限制位
}
void init() {
ten[0] = 1;
for (int i = 1; i < 15; i++) {
ten[i] = ten[i - 1] * 10;
}
memset(f, -1, sizeof(f));
}
int32_t main() {
cin>>t;
while(t--){
cin>>L>>R;
//handle(23);
init();//一定要记得初始化,TM的我在这卡了半个月
cout << handle(R)-handle(L) << endl;
delete[]a;
}
return 0;
}
如果要输出不带3 的数只要把上面的代码的函数修改一下就好了
dfs函数修改成这样:如果是带3的数就直接continue掉,而把不带3 的数加起来
int dfs(int p, bool limit) {//p表示在第p位,limite表示此时是否处于限制位
//求不带3的个数
if (p < 0)return 1;//说明位数全递归了
if (!limit && f[p] != -1)return f[p];//记忆化搜索,直接返回算过的值,减少计算时间
int up = limit ? a[p] : 9;
int ans = 0;
for (int i = 0; i <= up; i++) {
if (i == 3)continue;
ans += dfs(p - 1, limit && i == up);
}
if (!limit)f[p] = ans;//记忆化搜索
return ans;
}
handle函数修改:因为我们还要用到num所以num的值不能改变,要定义另外一个num来计算num的每一位数:
int handle(int num) {
int p = 0;
int x = num;
while (x) {//把num中的每一位放入数组
a[p++] = x % 10;
x /= 10;
}
//说明a数组写进去了,但是读取无效数据是什么意思勒,之前好像不是这样的,解决办法,动态创建数组
/*for (int i = 0; i < p; i++) {
cout << a[i];
}*/
return num + 1 - dfs(p - 1, true);//对应的最高位为p-1位,为True表示没有处于限制位
//在0到num中有num+1个数
}
下面就是算不带3的数的所有代码了:
#include<iostream>
using namespace std;
#define int long long
#define maxn 15
int num;
int* a = new int[maxn];
int f[15];
//int a[maxn];
int b[maxn];//b保存第p为存的是那个数
int ten[maxn];
int L, R;
int t;
int dfs(int p, bool limit) {//p表示在第p位,limite表示此时是否处于限制位
//求不带3的个数
if (p < 0)return 1;//说明位数全递归了
if (!limit && f[p] != -1)return f[p];//记忆化搜索,直接返回算过的值,减少计算时间
int up = limit ? a[p] : 9;
int ans = 0;
for (int i = 0; i <= up; i++) {
if (i == 3)continue;
ans += dfs(p - 1, limit && i == up);
}
if (!limit)f[p] = ans;//记忆化搜索
return ans;
}
int handle(int num) {
int p = 0;
int x = num;
while (x) {//把num中的每一位放入数组
a[p++] = x % 10;
x /= 10;
}
//说明a数组写进去了,但是读取无效数据是什么意思勒,之前好像不是这样的,解决办法,动态创建数组
/*for (int i = 0; i < p; i++) {
cout << a[i];
}*/
return num + 1 - dfs(p - 1, true);//对应的最高位为p-1位,为True表示没有处于限制位
//在0到num中有num+1个数
}
void init() {
ten[0] = 1;
for (int i = 1; i < 15; i++) {
ten[i] = ten[i - 1] * 10;
}
memset(f, -1, sizeof(f));
}
int32_t main() {
cin >> t;
while (t--) {
cin >> L >> R;
//handle(23);
init();//一定要记得初始化,TM的我在这卡了半个月
cout << handle(200) - handle(100) << endl;
delete[]a;
}
return 0;
}
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
Input
输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。
Output
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
Sample Input
1 100 0 0
Sample Output
80
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define LL long long
using namespace std;
int digit[10];
LL dp[10][2];
LL dfs(int len, int if6, int limit)
{
if (len == 0) return 1; //数到第0位返回1,因为0符合条件
if (!limit && dp[len][if6]) return dp[len][if6];
//如果不是上限,则不用重新数,直接返回,记忆化搜索的体现
LL ans = 0, up;
if (limit) up = digit[len];
else up = 9;
for (int i = 0; i <= up; i++)
{
if (if6 && i == 2) continue;
if (i == 4) continue;
ans += dfs(len - 1, i == 6, limit && i == digit[len]);
// limit&&i==up也可以
}
if (!limit) dp[len][if6] = ans;//记忆化搜索
return ans;
}
LL cal(LL x)
{
int len = 0;
while (x)
{
digit[++len] = x % 10;
x /= 10;
}
return dfs(len, 0, 1);
}
int main()
{
int n, m;
while (~scanf("%d%d", &n, &m))
{
if (!n && !m)
break;
// printf("%lld %lld\n",cal(m),cal(n));
printf("%lld\n", cal(m) - cal(n - 1));
}
return 0;
}
下面这个一样是不带49的数
题目:求区间[1,n]范围内包含带49的数。
一个数是带49的数,当且仅当他的十进制标示值中存在连续的两位,其中高位为4,较低位为9,比如49,149,1234987等;而4,12345,94,9999444都不是
输入格式:输入一个整数n(1<=n<=2^63)
输出格式:输出一个整数表示区间[1,n]范围内存在多少带49的数
输出样例:
输入:500
输出:15
#include<iostream>
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[22], f[22][10];//后面这个10用于确定前面那个数是哪一个
int n;
int t;
int dfs(int p, int pre, bool limit) {//p表示第p位数,pre表示p+1位数
if (p < 0) {
return 1;
}
if (!limit && f[p][pre] != -1) {
return f[p][pre];
}
int up = limit ? a[p] : 9;
int ans = 0;
for (int i = 0; i <= up; i++) {
if (pre == 4 && i == 9) {
continue;
}
ans += dfs(p - 1, i, limit && i == up);
}
if (!limit)f[p][pre] = ans;
return ans;
}
int handle(int num) {
int p = 0;
int x = num;
while (x) {
a[p++] = x % 10;
x /= 10;
}
return num + 1 - dfs(p - 1, 0, true);
}
void init() {
memset(f, -1, sizeof(f));
}
int32_t main() {
cin >> t;
while (t--) {
cin >> n;
init();
cout << handle(n) << endl;
}
return 0;
}