HDU - 4722 Good Numbers(数位DP)

本文介绍了一种利用数位动态规划(数位DP)解决特定类型数学问题的方法。通过实例解析,展示了如何计算两个数之间满足特定条件的数的数量,并深入解释了数位DP的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5969    Accepted Submission(s): 1886

题目链接:

HDU - 4722 

题目描述:

输入两个数A,B,判断从A到B之间有多少个数的各个位数上数字的和能够整除10

题解:

for(int i=le;i<=ri;i++)
        if(right(i)) ans++;

上面的方法是直接枚举,那这种方法可行不可行?

A,B的范围是1e18,那么这道题趋于时间的限制不能使用枚举法!

不过可以采用数位DP的方法去做这道题。

如果你对数位DP比较陌生的话,可以参考一下这篇博客

现在想要计算A到B中有多少个符合要求的数字,只需

solve(ri)-solve(le-1)

就可以得到结果

ll solve(ll x)
{
   int pos=0;//代表位数
   while(x)
   {
       a[pos++]=x%10;
       x/=10;
   }
   return dfs(pos-1,0,1);//因为是pos++,所以这里pos-1,这里已经是最高位了,这位的前面应该是0。
}

这里是solve()函数,这个函数的目的就是计算当前数之前有多少个数符合要求!

既然是数位DP,那就要说说什么是数位,就像123,1为百位,2位十位,3为个位,对每一位进行操作,然后结合dp进行优化。

那接下来有一个难点就是dfs(pos-1,0,1)了,1代表的是当前是最高位,比如说123,百位可以取0和1,如果说我现在取了1的话,这个时候dfs传入的就是1了,为什么呢?

举个例子:如果现在百位取1,那么我们的十位就只能取0/1/2,如果说现在百位取0,那么我们的十位就能取0-9里面的任意一个数,那么现在传入的1就代表我们后面要对前面位数上是不是最大数进行特判。

搜索函数如下:

ll dfs(int pos,int state,int limit)//limit判断是否达到上限
{
    if(pos==-1) return state==0;//如果pos==-1的话,就代表结束了,开始回溯
    if(!limit&&dp[pos][state]!=-1)//没有达到上限且存在
        return dp[pos][state];
    int up=limit?a[pos]:9;//计算下一位的位数
    ll ans=0;
    for(int i=0;i<=up;++i)
    {
        /*
        数位DP的模板上面这里有判断的条件,这道题可以直接用state解决。
        前面有一句if(pos==-1) return state==0;
        因为每一步进行的操作是(state+i)%10,所以只需使最后等于0就满足
        题目要求了,这样直接可以让ans++;
        */
        ans+=dfs(pos-1,(state+i)%10,limit&&i==up);
    }
    if(!limit) dp[pos][state]=ans;
    return ans;
}

这里其实有套用数位dp的模板,在for循环里面有的时候会加if进行相应的判断,举个例子,如果说现在要解决一道不能出现4或者连续出现6和2的数位dp问题,dfs()函数后半部分代码如下:

for(int d=0;d<=u;++d)
{
    if(d==4||(s&&d==2)) continue;
    ans+=dfs(len-1,d==6,e&&d==u)
}

这里u代表的是最高位的数字,s就是上一层dfs的d==6,也就是说如果现在d==6,那么s就等于1了,下面的那一个数如果是2的话,就直接continue。

再回到本题中来,本题中类似上面例子中的if的语句就是:

1、if(pos==-1) return state==0;

2、(state+i)%10

如果说pos==-1的话,就说明这一轮已经结束了,要是state等于0就代表当前的过程符合题目中的要求,ans++;

 

#include<algorithm>
#include<cstdio>
#include<string.h>
#include<string>
#include<iostream>
using namespace std;
typedef long long ll;
int a[25];
ll dp[25][20];

ll dfs(int pos,int state,int limit)//limit判断是否达到上限
{
    if(pos==-1) return state==0;//如果pos==-1的话,就代表结束了,开始回溯
    if(!limit&&dp[pos][state]!=-1)//没有达到上限且存在
        return dp[pos][state];
    int up=limit?a[pos]:9;//计算下一位的位数
    ll ans=0;
    for(int i=0;i<=up;++i)
    {
        /*
        数位DP的模板上面这里有判断的条件,这道题可以直接用state解决。
        前面有一句if(pos==-1) return state==0;
        因为每一步进行的操作是(state+i)%10,所以只需使最后等于0就满足
        题目要求了,这样直接可以让ans++;
        */
        ans+=dfs(pos-1,(state+i)%10,limit&&i==up);
    }
    if(!limit) dp[pos][state]=ans;
    return ans;
}

ll solve(ll x)
{
   int pos=0;//代表位数
   while(x)
   {
       a[pos++]=x%10;
       x/=10;
   }
   return dfs(pos-1,0,1);//因为是pos++,所以这里pos-1,这里已经是最高位了,这位的前面应该是0。
}

int main()
{
    ll le,ri;
    int n;
    cin>>n;
    int number=0;
    memset(dp,-1,sizeof(dp));
    while(n--)
    {
        scanf("%I64d%I64d",&le,&ri);
        if(le!=0)
          printf("Case #%d: %I64d\n",++number,solve(ri)-solve(le-1));
        else
          printf("Case #%d: %I64d\n",++number,solve(ri));
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值