7.5 数位DP
所谓数位DP,就是某些问题如果以数作为状态的属性进行转移,数值过大会导致时间和空间的复杂度过高。然而问题中n位数的情况可以由i位数(1<=i<n)的情况直接得到,于是我们可以以数的长度和数某位的值作为状态的属性进行转移。
下面以一道题为例。
hdu 2089 “不要62”
一个数字,如果包含4或者62,它是不吉利的。给定m和n,统计m~n中吉利数的个数。
分析:如果直接用dp[i]表示1-i,当n的值过大时显然复杂度太高。考虑用数位dp,对于长度为i的符合条件的数,在它的首位之前加上1,2,3,5,7,8,9都一定能构成一个长度为i+1的符合条件的数;如果长度为i的符合条件的数首位不是2,那么首位加上6也是一个长度为i+1的符合条件的数。
那么我们定义状态dp[i][j],表示i位数中首位是j符合条件的数的个数(j能够为0),那么有状态转移方程如下:
dp[i][j]=Σdp[i-1][k],0<=k<=9,j≠4,k≠2&&j≠6。
这么做比起直接以数作为状态的属性进行dp的复杂度要小很多。
#include<cstdio>
using namespace std;
const int len=18;//表示数字的最大长度
int dp[len+1][10];//dp[i][j]表示i位数,第一个数是j时符合条件的数字数量
int digit[len+1];//digit[i]存储n的第i位数字
void init(){dp[0][0]=1;
for (int i=1;i<=len;i++) for (int j=0;j<10;j++) for (int k=0;k<10;k++){
if ((j!=4)&&(k!=2&&j!=6)) dp[i][j]+=dp[i-1][k];//i位数的首位不能等于4,
}
}//计算[0,n]区间满足条件的数字个数
int solve(int l){int ans=0;
for (int i=l;i>=1;i--){//从高位到低位进行处理
//例如n=324561,首先就要加上第一个数为0,1,2的满足条件的六位数,这样我们就能得到0-299999的符合条件的数的个数
//再求300000-324561的符合条件的数的个数,就相当于求0-24561的符合条件的数的个数
//同理接下来就是求320000-324561的数的个数,就是相当于0-4561的数的个数
//但此时如果要求324000-324561的符合条件的数的个数,因为第三位一定为4,那么显然后面的数都不符合条件,直接break
for (int j=0;j<digit[i];j++) if (j!=4) ans+=dp[i][j];
if (digit[i]==4){ans--; break;}
if (i>1&&digit[i]==6&&digit[i]==2){ans--; break;}
} return ans;
}
int main(){int a,b,l1,l2,A,B; init();
while(scanf("%d%d",&a,&b)&&(a||b)){a--; l1=0; l2=0;
while (a){digit[++l1]=a%10; a/=10;} A=solve(l1);
while (b){digit[++l2]=b%10; b/=10;} B=solve(l2);
printf("%d\n",B-A);
}
}