算法学习:数位DP
【解决问题】
一个极大的多位数字中,存在某种特殊情况出现多少次
通过记忆化和动态规划的方法,将之前的情况数记录下来,在计算其他情况的时候,通过状态的转移相互利用
【模板】
【题意】求1~n之间有多少个数又含有13又能被13整除,(1<=n<=1e9)
【代码】
#include<cstdio>
#include<cstring>
#define MAXN 20
int dp[MAXN][MAXN][10];
int maxx[MAXN];
//maxx记录整个数字的上限
//len 表示当前枚举的数字长度
//mod 表示状态之一,现在这个长度对16取余的结果
//stu 状态之一,当前的数字是否存在
//lim 状态之一,前面的数字是否达到上限,因为枚举的情况在前面没有达到的话,后面的是可以任取的
// 而如果达到的话,就需要考虑数字的取值范围了
int Geta(int len,int mod,int stu,bool lim)
{
//从后往前开始枚举数字的情况
if(len==0)
//当所有位置上的数字列举完成之后
return mod==0&&stu==2;
//返回存在的情况
if(!lim && dp[len][mod][stu])
return dp[len][mod][stu];
//max 表示需要遍历到的最大的数字
int cnt=0,max=lim?maxx[len]:9;
for(int i=0;i<=max;i++)
{
int ns=stu;
//状态的转换
if(stu!=2&&i==1)//如果数字没有13,并且此位为1,那么标记为状态1
ns=1;
if(stu!=2&&i!=1)//如果此位不为1,那么就什么都没有,即为0
ns=0;
if(stu==1&&i==3)//如果前一位是1,并且这一位是3的话,这个数字就拥有了13
ns=2;
//注意顺序
cnt+=Geta(len-1,(mod*10+i)%13,ns,lim&&i==max);
//将短的情况进行枚举,这里枚举的i就是当前位置的数字
// lim && i==max 是否达到上限,如果lim没有达到,后面取什么都无所谓了
// 如果前面达到,但是当前位置达到
}
if(!lim)//没有超过的话,就将计数返还
dp[len][mod][stu]=cnt;
return cnt;
}
int main()
{
int n;
while(~scanf("%d",&n))
{
memset(dp,0,sizeof(dp));
int len=0;
while(n)
{
maxx[++len]=n%10;
n/=10;
}
printf("%d\n",Geta(len,0,0,1));
}
}