51nod1009 51nod1042(数位dp)

51nod1009数字1的数量


 

题目意思就是求1到n出现过几个1(例如n为12时,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1)

题目思路就是数位dp,其实可以找规律,但本人太懒了,太麻烦了不想找。数位dp首先要知道dp记录的是什么,这里dp[i][j]指0到第i位数值为j的1的数量(比如dp[3][2](没有上限的情况下)则记录的是0到299有多少个1)。这里会遇到三种情况:

1.j不是1,则继续搜索

                                             

2.j是1,并且不是上限则dp[i][j]=pow(10,i-1)+ dp[1~i-1][0~9(或上限)](比如n=213,dp[3][1]指的就是100到199 有pow(10,2)个1再加低位的数量即0到199中1的个数)

                                 for(int i=j-1;j>=1;j++)

3,j是1,并且是上限则dp[i][j]= +   a[i]*pow(10,i-1)   +1 +dp[1~i-1][0~9(或上限)](比如n=213,dp[2][1]指的就是10到13所以是4个这是+1的原因(1后面全是0的情况),再加低位的数量即0到13中1的个数)

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define inf 0x3f3f3f3f
#include<cmath>
using namespace std;
int dp[15][15];//dp[i][j]指0到第i位 数值为j的1的个数 
int a[15];//a[i]指n第i位的数 
int dfs(int pos,int num,bool limit)
{
    if(pos==0)
        return 0;
    if(!limit&&dp[pos][num]!=-1)//已经被记忆了,直接用 
        return dp[pos][num];
    int ans=0;
    int k=limit?a[pos-1]:9;
    if(num==1)
    {
        if(limit)//该数字是1,正好是上限,例如1342 那么他有342个数再加1(1000)个有1 的数(即343个) 
        {
            for(int i=pos-1;i>=1;i--)
                ans+=a[i]*pow(10,i-1);
            ans++;//1000..的情况 
        }
        else//没有上限则就是10^(pos-1) 比如1000 他就是1000个1 
        {
            ans+=pow(10,pos-1);    
        } 
    }
    for(int i=0;i<=k;i++)
    {
        ans+=dfs(pos-1,i,limit&&i==a[pos-1]);//搜索下一位 
    }
    if(!limit)//记忆化 
        dp[pos][num]=ans;
    return ans;
}
int solve(int n)
{
    int len=0;
    while(n)
    {
        a[++len]=n%10;
        n/=10;
    }
    return dfs(len+1,-1,true);
}
int main()
{
    int n;
    memset(dp,-1,sizeof(dp));
    while(scanf("%d",&n)!=EOF)
        printf("%d\n",solve(n));
    return 0;    
} 

51nod 1042数字0-9的数量

这题是上面那题延伸过来的,求n到m中0-9出现的个数

开始以为很简单,直接在前面的dfs再加一个sum记录0-9,发现0的时候出问题了。

他有前导零的情况也算计去了,比如19他会算00-09多算了10个。所以这题要在0的情况在加一个前导零的变量控制,其他1-9直接用上面的代码。0的情况只比前面多一种情况,就是前导零不加值。这里要注意的是不能再用pow()函数了,pow()只会返回int型数值,这里要用long long 的数值,所以要写一个快速幂。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define inf 0x3f3f3f3f
#include<cmath>
using namespace std;
ll dp[25][25];
ll a[25];
ll poww(ll a,ll b)//快速幂 
{
    ll ans=1;
    while(b)
    {
        if(b&1)
            ans*=a;
        a*=a;
        b>>=1;
    }
    return ans;
}

ll dfs(int pos,int num,bool limit,bool lead,int sum)//lead是否为前导零,是就是true,sum就是求0-9 
{
    if(pos==0)
        return 0;
    ll ans=0;
    int k=limit?a[pos-1]:9;
    if(!limit&&dp[pos][num]!=-1&&!lead)//如果sum是0 ,且为前导零则不能用记忆的dp了. 
        return dp[pos][num];
    if(num==sum)
    {
        if(sum==0)//特判0的情况 
        {
            if(limit&&!lead)//不是前导零 
            {
                for(int i=pos-1;i>=1;i--)
                    ans+=a[i]*poww(10,i-1);
                ans++;
            }
            else if(!limit&&!lead)
                ans+=poww(10,pos-1);
            else if(lead)//只有前导零有区别,不要加值 
                ans+=0;
        }
        else
        {
            if(limit)
            {
                for(int i=pos-1;i>=1;i--)
                    ans+=a[i]*poww(10,i-1);
                ans++;
            }
            else
                ans+=poww(10,pos-1);    
        }
    }
    for(int i=0;i<=k;i++)
    {
        ans+=dfs(pos-1,i,limit&&i==a[pos-1],lead&&i==0,sum);
    }
    if(!limit&&!lead)
        dp[pos][num]=ans;
    return ans;
}
ll solve(ll n,int i)
{
    int len=0;
    while(n)
    {
        a[++len]=n%10;
        n/=10;
    }
    if(i==0)
        return dfs(len+1,-1,true,true,i); 
    else
        return dfs(len+1,-1,true,false,i);//不是0,就不需要前导零,可以直接赋false,前面的dfs就不会用影响 
}
int main()
{
    ll n,m;
    while(scanf("%lld%lld",&n,&m)!=EOF)
    {
        for(int i=0;i<=9;i++)
        {
            memset(dp,-1,sizeof(dp));//这里每个数要清空一次dp数组 ,因为他们代表的值不一样 
            printf("%lld\n",solve(m,i)-solve(n-1,i));
        }
    }
    return 0;    
} 

做了几天只理解成这样,虽然在努力想讲好,但是还是很烂。。。再苦再难,也要前行!

贴个讲数位dp一个博客https://blog.csdn.net/wust_zzwh/article/details/52100392

 

转载于:https://www.cnblogs.com/xiongtao/p/9312686.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值