【概述】
数位 DP 实际是一种计数用的 DP,一般就是统计一个区间 [le,ri] 内满足一些条件数的个数。
所求的限定条件往往与数的位数有关,例如:数位之和、指定数码个数、数的大小顺序分组等。
题目所给的区间范围往往很大,无法通过暴力枚举来求解,一般是要求把某个区间的符合某种特征的数的个数求出来,因此根据根据 “逐位确定” 的基本思想,将最大数按位分解,然后 dfs 依次判断每一位相应的数是否满足要求。
【基本思想】
数位 DP 的实质就是一种暴力枚举的方式,使得新的枚举方式满足 DP 的性质,然后进行记忆化搜索。
对于一个求区间 [le,ri] 满足条件数的个数,最简单的暴力方式为:
for(int i=le;i<=ri;i++)
if(right(i))
ans++;
但这样枚举没有任何状态,不便于进行记忆化,因此,可以控制上界,从最高位向下枚举。
例如:要求比 456 小的数,可以如下考虑
4 5 6
4 5 (0~6)
4 (0~4) (0~9)
(0~3) (0~9) (0~9)
此时,这种新的枚举只控制了上界,统计 [1,ri] 和 [1,le-1] 的数量,然后相减即为区间 [le,ri] 的数量,需要注意的是 le 的范围都是大于等于 1 的
int main()
{
long long le,ri;
while(~scanf("%lld%lld",&le,&ri))
printf("%lld\n",solve(ri)-solve(le-1));
}
【模板】
枚举到当前位置 pos,状态为 state 的数量,dp 值保存的是满足条件数的个数。
typedef long long LL;
using namespace std;
int a[20];
LL dp[20][state];//不同题目状态不同
/*
pos 枚举到当前位置
lead 前导零,不是每个题都要判断
limit 数位上界变量
*/
LL dfs(int pos,state变量,bool lead,bool limit)
{
/*
递归边界,由于按位枚举,最低位是0,此时说明枚举完了
此时一般返回1,表示枚举的这个数是合法的,
因此要保证枚举时每一位都要满足题目条件,
即当前枚举到pos位,
*/
if(pos==-1)
return 1;
if(!limit && !lead && dp[pos][state]!=-1) //记忆化
return dp[pos][state];
int up=limit?a[pos]:9;//根据 limit 判断枚举的上界
LL ans=0;
for(int i=0;i<=up;i++)//枚举
{
/*把不同情况的个数加到ans*/
if()
...
else if()
...
else
...
ans+=dfs(pos-1,状态转移,lead && i==0,limit && i==a[pos])
}
if(!limit && !lead)//计算完,记录状态
dp[pos][state]=ans;
return ans;
}
LL solve(LL x)
{
int pos=0;
while(x)//分解数位
{
a[pos++]=x%10;//存储各数位
x/=10;
}
return dfs(pos-1,一系列状态,true,true);//从最高位开始枚举
}
int main()
{
LL le,ri;
while(~scanf("%lld%lld",&le,&ri))
{
memset(dp,-1,sizeof(dp));//一般初始化为-1
printf("%lld\n",solve(ri)-solve(le-1));
}
}