数位dp(1)
HDU 3555 Bomb
题意:
给你x,求小于等于x的所有正整数中包含49的数的个数。
题解:
最基础的数位dp。solve(x)表示小于等于x的数中包含49的数的个数。问题就在于如何实现solve函数。为了实现solve函数,我们需要使用一个dfs函数:dfs(x,b,lim)。返回值为满足条件的数的个数,x表示当前枚举的位数,b表示当前状态(b=2表示已经出现过49,b=1表示前一个数为4,b=0表示其他情况),lim是最重要的一点,即当前数位的取值有没有限制。
我们举一个解释lim作用的例子,如果当前的数为5678,一开始我们调用dfs(4,0,1),所以当前位只能最大取到5,接下来我们从0到5枚举当前位的取值,如果当前位是小于5,接下来就没有限制,因为0XXX,1XXX,2XXX,3XXX,4XXX后面的那些XXX是可以从000取到999的,并不给它加以任何限制,然而对于5XXX,下一位只能取到0到6,因为57XX就已经大于当前的数了,所以lim还是只能设为1。
接下来还有一个优化,可以发现如果lim=0,x和b相等的情况下答案是肯定相等的,而lim!=0则不一定,因为枚举的范围和当前数有关。所有我们可以用记忆化搜索的方法来优化复杂度。
接下来给出代码:
#include<cstdio> #include<cstring> typedef long long ll; ll f[20][3]; int a[20],cnt; ll dfs(int x,int b,bool lim){ if(x==0) return b==2; //搜到头了 if(!lim&&f[x][b]!=-1) return f[x][b]; //记忆化搜索 ll ans=0; int maxl=lim?a[x]:9; //maxl表示当前能枚举到的最大数 for(int i=0;i<=maxl;++i) ans+=dfs(x-1,(b==2||(b==1&&i==9))?2:i==4,lim&&i==maxl); //注意这里的递归调用 if(!lim) f[x][b]=ans; return ans; } ll solve(ll x){ for(cnt=0;x;x/=10) a[++cnt]=x%10; //把这个数的每一位都存下来 return dfs(cnt,0,1); } int main(){ memset(f,-1,sizeof f); int t; scanf("%d",&t); for(ll x;scanf("%lld",&x)!=EOF;printf("%lld\n",solve(x))); return 0; }
注意:
如果有多组数据,不需要每组数据都将f数组memset一遍,因为当lim等于0时,dfs函数的结果和当前数字并没有关系。
有很多数位dp题是求[l,r]中符合要求的数的个数,只要返回solve(r)-solve(l-1)就行了。