zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",solve(m,i)-solve(n-1,i));
            }
        }
        return 0;    
    } 

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

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

  • 相关阅读:
    对于高并发短连接造成Cannot assign requested address解决方法
    virtualbox迁移已建虚机存储磁盘方法
    httpd:RSA certificate configured for SERVER does NOT include an ID which matches the server name
    解决服务不断重启挂掉问题
    tbb静态库编译
    su和su
    ubuntu18.04 mariadb start失败
    如何修复“sshd error: could not load host key”
    [LeetCode]Gas Station
    Java多态的一些陷阱
  • 原文地址:https://www.cnblogs.com/xiongtao/p/9312686.html
Copyright © 2011-2022 走看看