昨天做了一道很奇怪的数字题,不知道怎么做,今天才知道是数位DP ……我来学习学习。
传送门
大意:给定区间
[n,m]
,求在n到m中没有“62“或“4“的数的个数。如62315包含62,88914包含4,这两个数都是不合法的。
思路:做这道题我们就要用到数位DP了,我们可以线预处理出一个F数组。用 F[i,j] 代表开头是j的i位数中不含”62”或”4”的数有几个。这样我们很好写出一个状态转移方程 F[i,j]=F[i−1,k]|其中j≠4且当j=6时k≠2 。我们做的时候,从高到低枚举哪一位比n的哪一位小(这是可以为0的,我们枚举的时候不能取等就是因为我们算的是比他小的。比如说我们找一个数52496:首先答案会加上f[5][3]+f[5][2]+f[5][1]+f[5][0]+f[4][1]+f[4][0]然后就不加了,分别表示三开头的五位数,二开头的五位数,一开头的五位数,任意位数非5位数,以五一开头的五位数,以五零开头的五位数)是不是很易懂呢?
上代码:
#include<cstdio>
#include<cstring>
int f[10][10], n, m;
int bit[10];
int dp(int len)
{
int ans = 0;
bit[len + 1] = 0;
for(int i = len; i > 0; i --){
for(int j = 0; j < bit[i]; j ++){
if(j == 2 && bit[i+1] == 6) continue;
ans += f[i][j];
}
if(bit[i] == 4||(bit[i] == 2 && bit[i+1] == 6)) //找到了4或者连续的62说明后面的数都是接在4和62后面的,所以直接退出
break;
}
return ans;
}
int main()
{
f[0][0] = 1;
for(int i = 1; i <= 7; i ++)
for(int j = 0; j <= 9; j ++)
{
if(j == 4) continue;
for(int k = 0; k <= 9; k ++)
{
if((j == 6 && k == 2) || k == 4) continue;
f[i][j] += f[i-1][k];
}
}
while(~scanf("%d%d", &n, &m) && n + m) {
m ++; //因为我们只算了[1,r)的,所以上去间 ++
int t1 = n, t2 = m, l1 = 0, l2 = 0;
while(t1)
{
l1 ++;
bit[l1] = t1 % 10;
t1 /= 10;
}
t1 = dp(l1);
while(t2)
{
l2 ++;
bit[l2] = t2 % 10;
t2 /= 10;
}
t2 = dp(l2);
printf("%d\n", t2 - t1);
}
return 0;
}