什么是数位dp
数位dp是一种在数位上操作用于计数的dp,一般用于统计一个区间[l,r]内满足某些条件数的个数
那什么是数位呢?一个数有个位、十位、百位、千位等等,数的每一位都是数位
其实,数位dp的实质就是换一种暴力枚举的方式使得新的枚举方式满足dp的性质,然后进行记忆化就可以了。
当大家看到类似于统计一个区间[L,R]中满足某些条件数的个数时,且此时L,R都会取1e9~1e18这么大的范围时,百分之90的情况下都用数位dp!!
数位dp的思路
理解数位dp就须理解新的枚举方式:控制上界枚举,就是从最高位开始往下枚举
例如:r=213,
我们从百位开始枚举:百位上可能的情况有0,1,2,然后每一位枚举都不能让枚举的这个数超过上界213,当百位上枚举了0或1,那么十位上枚举就从0到9,因为百位上0或1比上界2小,后面的数位无论枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界时,那么紧接着的一位枚举就有上界限制了。这个例子中如果百位枚举了2,那么十位的枚举情况就只能是0到1,如果前两位枚举了21,那么最后一位是0到3
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[8][10],digit[10]; //d为存储上界的各个数位上的数
//dp[i][j]是第i位数首位是j的方法数
void init() //数位dp的初始化
{
dp[0][0]=1; //初始化
for(int i=1;i<=7;i++) //遍历位数
for(int j=0;j<=9;j++) //j为i的首位数
for(int k=0;k<=9;k++) //k为i-1的首位数
if(j!=4&&!(j==6&&k==2)) //当j!=4且!(j==6&&k==2)时进行累加
dp[i][j]+=dp[i-1][k]; //状态转移方程
}
int solve(int n)
{
int ans=0,len=0;
while(n!=0)
{ //例如1234,存储在数组中是4321
len++;
digit[len]=n%10;
n=n/10;
}
digit[len+1]=0; //因为dp需要它的前一位数所以置为0
for(int i=len;i>=1;i--) //从高位枚举
{
for(int j=0;j<digit[i];j++) //枚举范围内的每一个数字
{
if(j!=4&&!(digit[i+1]==6&&j==2)) //如何当前位置合适,则加入方案数
ans+=dp[i][j];
}
if(digit[i]==4||(digit[i]==2&&digit[i+1]==6)) break;
//第i位已经不满足条件,则i位以后都不可能满足条件,结束循环
}
return ans;
}
int main()
{
int n,m;
init();
while(cin>>n>>m)
{
if(n==0&&m==0) break;
cout<<solve(m+1)-solve(n)<<endl;
//solve(n)返回的是[0,n)满足条件个数,由于要取得的范围是[m,n],所以用solve(n+1)-solve(m)
}
return 0;
}
刚开始可能刚学脑子有点懵,为什么从高位枚举是从len开始,因为digit数组存储数字是从个十百千万存的,例如1234存储在数组中是4321
另一个没懂的地方是在solve函数,例如40,第一遍for循环就结束了,那个位数的解决方案算进去了吗?j可以为0,所以01、02之类的算进去的,因为加了dp[len][0]的值