题目链接:HDU2089不要62
这道题目没有做出来,看来题刷的还不够。问题大致意思是:给出整数区间,求出在区间中的数不含4和62的个数。
一开始我觉得这道题目挺简单的,一来就暴力求解,结果超时,实在不知道怎么改,后来参考了大佬的初学数位DP–hdu 2089才对数位dp有所了解。
下面我们以[ 0 , 3548 ]这个区间来了解数位dp
这与数位区间有关,不能暴力求解,必须在数位上进行递推操作等。
要知道:两个数在进行比较时,我们是从两个数的高位到低位的顺序(如果顺序相反则不行)依次进行比较,当一个数a的高位数小于另一个数b的对应的高位数时,无论a的高位数后的数怎么取值,a始终是小于b的。例如:3548和32xx比较,百位5已经大于了百位2,因此,不管xx是什么,32xx都要小。
按照这个逻辑:我们在求[ 0 , 3548 ]这个区间满足条件的个数时,我们所求个数是等于区间[ 0 , 3000 ],[ 0 , 500 ],[ 0 , 40 ],[ 0 , 8 ]这四个区间所满足条件的个数的和。
另外当我们算到计算到xx4x时,4后面不管取何值,这个数都不满足条件,因此可以直接跳过,以节省时间。对于上面的四个区间的满足条件的数的个数,我们就要用到数组dp[ i ][ j ],这个数组的含义是:以 j 开头的 i 位数满足条件的个数。
for(int i=1;i<=7;i++) //n最大是7位数
{
for(int j=0;j<10;j++)//枚举第i位可能出现的数(10种)
{
for(int k=0;k<10;k++)//枚举第i-1位可能出现的数(也是10种)
{
if(j!=4&&!(j==6&&k==2))
dp[i][j] += dp[i-1][k];
}
}
}
这段代码是对dp数组进行初始化,第[ i ]行(即 i 位数)的数除了2,4,6开头外,以其他数开头的 i 位数符合条件的数的个数都是 i - 1 位数所有数字开头的总和(体现了递归的思想)。
当有xx6xx的情况,则dp[ i ][ 6 ] = dp[ i - 1 ][ 0 ~ 9 ] - dp[ i - 1 ][ 6 ],若以4开头,这个数就不符合条件,那么dp[ i ][ 4 ]直接为0。
初始化后的dp数组:
现在既然能够计算出[ 0 , n ]符合条件的个数,通过[ 0 , n ] - [ 0 , m )来得出[ m , n ]的符合条件的个数。
附上大佬的AC代码:
#include <iostream>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
int dp[10][10];
void init()
{
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for(int i=1;i<=7;i++)
{
for(int j=0;j<10;j++)//枚举第i位可能出现的数
{
for(int k=0;k<10;k++)//枚举第i-1位可能出现的数
{
if(j!=4&&!(j==6&&k==2))//当遇到4时,直接退出,如前面的3548,若遇到62也是同理.
dp[i][j] += dp[i-1][k];
}
}
}
}
int solve(int n)
{
init();
int digit[10];
int len = 0;
while(n>0)
{
digit[++len] = n%10;
n/=10;
}
digit[len+1]=0;
int ans = 0;
for(int i=len;i;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;
}
return ans;
}
int main()
{
int l,r;
while(cin>>l>>r)
{
if(l+r==0)
break;
else
cout<<solve(r+1)-solve(l)<<endl;
}
return 0;
}
最后,[ 0 , 3548 ]符合条件的个数 = 711 * 3 + 80 * 4 + 9 * 4 = 2489