我们有时候会遇到这样一类题目,给你一个区间 [l,r] ,找区间上符合某种特定要求的数的个数,这个要求可能很简单,很好理解,但是由于区间范围太大,以至于对每个数进行遍历判别是不太可能的,对于这种情况,就需要用数位DP来解决了。
for(int i = l;i<=r;i++)
if(check(i))ans++;
其中check函数是检验是否满足要求,ans是总个数,这样求解很容易超时,比如 l = 1,r = 1e9,时间上肯定是过不去的,于是我们就要考虑如何优化这个问题了。
这里说的可能有点抽象了,但是没有关系,我们以一道题目为例:给定一个区间 [l.r] ,找到这个区间上所有的“不减数”,不减数定义为从高位到低位数字大小是不减的,比如:223,669,456等等
那么这道题目我们首先使用第一个思想,假设dp(n)为区间 [0,n] 上的不减数的个数,那么最后的结果应该是dp(r) - dp(l-1),下边我们来详细介绍一下第二个优化思想,我们将数字n各位上的数字从高位到低位排列,如下图所示:
我们很容易能够理解,题目中要求的数实际上满足:
我们假设:f[i,j] 表示最高位为j,长度为i的数中,不降数的个数,所以我们有代码:
void init()
{
//长度为1的个数只有一个
for(int i=0;i<=9;i++) f[1][i]=1;
//长度大于1时
for(int i=2;i
下边我们考虑数位,我们将这个数字竖着来写,当我们选择数字时,比如当n=5863时,那么最高位就是5,很明显我们只能取0~5,下边我们将分两种情况,第一种是取0~4,此时只要以0~4开头的所以数是存在的,于是f[0~4][m]就是该部分的不降数个数;对于另一种情况,最高位取了5,那么下一位并不能取遍0~9,所以需要再做一次分类。
如下图所示,当第二位取0~7时,那么后边的数字可以取遍0~9,所以满足条件个数为 f[0~7,m-1],对于取8的情况,还要继续分情况,到这里就很清楚的说明为什么数位DP可以用一颗树来形象说明了,中间过程是递归的,下边我给出解题代码:
#include
#include
#include
#include
using namespace std;
const int maxn=15;
int f[maxn][maxn];
void init()
{
//长度为1的个数只有一个
for(int i=0;i<=9;i++) f[1][i]=1;
//长度大于1时
for(int i=2;inums;
while(n) nums.push_back(n%10),n/=10;
int res=0;//方案数
int last=0;//保留一些前缀信息,本题是上一个数是几
///从最大数开始枚举
for(int i=nums.size()-1;i>=0;i--)
{
int x=nums[i];//x为当前这位数
for(int j=last;j=上一位,所以从last开始枚举,最多枚举到x,last为上一位,也即最高位,对下一位的枚举是有限制的
res+=f[i+1][j]; ///左端的节点有i+1个位数(因为第一位的下标是0)
if(x>l>>r)
{
cout<
题目链接:https://www.acwing.com/problem/content/1083/
这里还是解释一下题意:求符合要求的数的个数,要求为一个数字可以被表达为以下形式:
其中n和a代表一个数字,m1~mp是p个不同的数字,换言之,一个数字n可以写成a的不同幂的和,那么这个数字就是符合要求的。还是给一个区间 [l,r] 求区间中所有符合要求的数字的个数,我们可以将这道题目题解为,将n转换为a进制,该数字中各个位上1个个数正好有K个,我们定义 f[i,j] 代表后i位中包含j个1的数字的个数,初始化过程为:
void init(){
for(int i = 0;i<=N;i++)
for(int j = 0;j<=i;j++)
if(!j)f[i][j] = 1;
else f[i][j] = f[i-1][j]+f[i-1][j-1];
}
我还先将流程放上来:
如果当前位是0的话,那么该位只能取0,有res += f[i][K - last],last是已经使用了多少个1,如果当前为为1的话,那么last++;如果当前位大于1的话后边的数字将不受限制,res += f[i][K-last-1];break终止即可,下边是详细代码:
#include
#include
#include
using namespace std;
int X,Y,K,B;
const int N = 35;
int f[N][N];
//组合数
//f[i][j]表示i个数中随机先j个的组合数
void init(){
for(int i = 0;i<=N;i++)
for(int j = 0;j<=i;j++)
if(!j)f[i][j] = 1;
else f[i][j] = f[i-1][j]+f[i-1][j-1];
}
int dp(int n){
//特判
if(n==0)return 0;
//转化为B进制
vectornums;
while(n){
nums.push_back(n%B);
n = n/B;
}
int res = 0;
int last = 0;
for(int i = nums.size()-1;i>=0;i--){
int x = nums[i];
//如果x>0
if(x){
//该位取0
res += f[i][K - last];
if(x>1){
//大于1,后边取值不受限制,终止
if(K-last-1>=0)res += f[i][K-last-1];
break;
}
else {
//等于1,用掉一个1
last++;
if(last>K)break;
}
}
//特判
if(i==0&&last==K)res++;
}
return res;
}
int main(){
init();
cin>>X>>Y>>K>>B;
cout<
https://www.acwing.com/problem/content/1083/
https://www.acwing.com/solution/content/10735/