算法学习:数位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));
        }
}
 

猜你喜欢

转载自www.cnblogs.com/rentu/p/12295558.html
今日推荐