关闭

数位dp简单题目汇总

标签: hdudpuestcpojlightoj
1626人阅读 评论(0) 收藏 举报
分类:

HDU2089

地址:http://acm.hdu.edu.cn/showproblem.php?pid=2089


思路:数位dp模板题。纯利用数位dp来做。先打个dp[i][j]的表格,表示以j开头的i位数符合情况的有多少个数字。然后分别求出前m、n个数字中符合情况的有多少个,两答案相减即可得出最终结果。

代码:

#include<iostream>
#include<cmath>
//#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//#define M 1000000007
int main()
{
    int i,j,k,m,n,dp[10][20]={0};
    dp[0][0]=1;
    for(i=1;i<=7;i++)  //打表
    {
        for(j=0;j<=9;j++)
            for(k=0;k<=9;k++)
            if(j!=4&&k!=4&&!(j==6&&k==2))
            dp[i][j]+=dp[i-1][k];
    }
    while(scanf("%d%d",&m,&n)>0,m||n)
    {
        int len1,len11[10]={0},len2,len22[10]={0};
        len1=len2=0;
        while(m)
        {
            len11[len1++]=m%10;
            m/=10;
        }
        while(n)
        {
            len22[len2++]=n%10;
            n/=10;
        }
        int ans=0;
        for(i=len1-1;i>=0;i--)  //求出m之前有多少数字符合情况
        {
            for(j=0;j<len11[i];j++)
            {
                if(j!=4&&!(j==2&&len11[i+1]==6))
                    ans-=dp[i+1][j];
            }
            if(j==4||(j==2&&len11[i+1]==6)) break;
        }
        for(i=len2-1;i>=0;i--)  //求出n之前有多少数字符合情况
        {
            for(j=0;j<len22[i];j++)
            {
                if(j!=4&&!(j==2&&len22[i+1]==6))
                    ans+=dp[i+1][j];
            }
            if(j==4||(j==2&&len22[i+1]==6)) break;
        }
        for(i=0;i<len2;i++)  //判断m是否符合情况
            if(len22[i]==4||(len22[i]==2&&len22[i+1]==6)) break;
        if(i==len2) ans++;
        printf("%d\n",ans);
    }
    return 0;
}


HUD3555

地址:http://acm.hdu.edu.cn/showproblem.php?pid=3555


题意:和上一题很像,这题是求在n之前包含49的数字有多少个。

思路:纯数位dp,直接模板,不解释。

代码:

#include<iostream>
#include<cmath>
//#include<vector>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//#define M 1000000007
int main()  //我这里连变量都懒得改了,不过需要注意INT64
{
    __int64 i,j,n,k,t,dp[22][11][2]={0};
    dp[0][0][0]=1;
    for(i=1;i<=20;i++)
    {
        for(j=0;j<=9;j++)
            for(k=0;k<=9;k++)
            {
                if(k==9&&j==4)
                    dp[i][j][1]+=dp[i-1][k][0];
                else dp[i][j][0]+=dp[i-1][k][0];
                dp[i][j][1]+=dp[i-1][k][1];
            }
    }
    scanf("%I64d",&t);
    while(t--)
    {
        scanf("%I64d",&n);
        __int64 len2=0,len22[30]={0};
        while(n)
        {
            len22[len2++]=n%10;
            n/=10;
        }
        __int64 ans=0,bg=0;
        for(i=len2-1;i>=0;i--)
        {
            for(j=0;j<len22[i];j++)
            {
                ans+=dp[i+1][j][1];
                if(bg) ans+=dp[i+1][j][0];
            }
            if(j==9&&len22[i+1]==4) bg=1;
        }
        if(bg) ans++;
        printf("%I64d\n",ans);
    }
    return 0;
}

uestc1307

地址:http://www.acm.uestc.edu.cn/problem.php?pid=1307


思路:打表,基础数位dp。
代码:
/*这里的主要问题在于处理位数小于输入边界数字的数*/
#include<iostream>
#include<cstdio>
#include<cstring>
//#include<algorithm>
using namespace std;
#define LL long long int
LL dp[20][20]={0};
void getdp()
{
    int i,j;
    for(i=0;i<=9;dp[1][i]=1,i++);
    for(i=2;i<=10;i++)
    {
        for(j=0;j<=9;j++)
            for(int k=0;k<=9;k++)
            if(k-j<=-2||k-j>=2) dp[i][j]+=dp[i-1][k];
    }
}
LL getans(LL m)
{
    LL len=1,len1[20]={0},ans=0;
    while(m)
    {
        len1[len++]=m%10;
        m/=10;
    }
    for(int i=1;i<len-1;i++)
        for(int j=1;j<=9;j++)
        ans+=dp[i][j];
    for(int i=1;i<len1[len-1];i++)
        ans+=dp[len-1][i];
    for(int i=len-2;i>0;i--)
    {
        for(int j=0;j<len1[i];j++)
            if(len1[i+1]-j>=2||len1[i+1]-j<=-2) ans+=dp[i][j];
        if(len1[i+1]-len1[i]<2&&len1[i+1]-len1[i]>-2) break;
    }
    /*本来在这里的代码是这样的
    for(int i=len-1;i>0;i--)
    {
        for(int j=0;j<len1[i];j++)
            if(i==len-1||len1[i+1]-j>=2||len1[i+1]-j<=-2) ans+=dp[i][j];
        if(i!=len-1) ans+=(dp[i][0]+dp[i][1])
        if(len1[i+1]-len1[i]<2&&len1[i+1]-len1[i]>-2) break;  //但是这里有个break,所以不能全部加上,所以这里看了下别人的代码,改用三个for循环
    }*/
    return ans;
}
int main()
{
    LL l,r;
    getdp();
    while(scanf("%lld%lld",&l,&r)>0)
        printf("%lld\n",getans(r+1)-getans(l));
    return 0;
}

lightoj1140

地址:http://www.lightoj.com/volume_showproblem.php?problem=1140


题意:问两个数之间有多少个数字“0”。

思路:本来想练习下搜索做法,但是越做越迷。最后还是用了打表的方法。注意的地方有两处,一个是打表时关于数位0的处理,另一个是求答案时关于数位0的处理。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define LL long long
LL dp[30][10]={0};
void getdp()
{
    for(int i=1;i<27;i++)
    {
        for(int j=0;j<=9;j++)
            for(int k=0;k<=9;k++)
                dp[i][j]+=dp[i-1][k];
        dp[i][0]+=(LL)pow(10.0,i-1);  //打表时关于数位0的处理
    }
}
LL getans(LL m)
{
    int len=0,num[30]={0};
    LL ans=0,n=m;
    while(n)
    {
        num[++len]=n%10;
        n/=10;
    }
    for(int i=len;i>0;i--)
    {
        for(int j=0;j<num[i];j++)
            ans+=dp[i][j];
        if(num[i]==0)
            ans+=(m%(LL)pow(10.0,i-1));  //若给出的左右端数字含有0
        if(i-1) ans-=(LL)pow(10.0,i-1);  //求答案时关于数位0的处理
    }
    return ans;
}
int main()
{
    getdp();
    int cas=1,t;
    LL m,n;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld%lld",&m,&n);
        printf("Case %d: %lld\n",cas++,getans(n+1)-getans(m));
    }
    return 0;
}


lightoj1032

地址:http://www.lightoj.com/volume_showproblem.php?problem=1032

题意:二进制数中连续两个‘1’出现次数的和

思路:把输入的数转为二进制,打表也打二进制的表。其他的和一般的数位DP一样。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define LL long long
LL dp[40][2];
void getdp()
{
    memset(dp,0,sizeof(dp));
    for(int i=2;i<40;i++)
    {
        for(int j=0;j<2;j++)
            for(int k=0;k<2;k++)
                dp[i][j]+=dp[i-1][k];
        dp[i][1]+=(LL)pow(2.0,i-2);  //这里注意一下,两个连续的1出现不止加一次就够了
    }
}
LL getans(LL m)
{
    int len=0,num[40]={0};
    LL ans=0,n=m;
    while(n)
    {
        num[++len]=n%2;
        n/=2;
    }
    for(int i=len;i>0;i--)
    {
        for(int j=0;j<num[i];j++)
            ans+=dp[i][j];
        if(num[i]&&num[i+1]) ans+=(m%((LL)pow(2.0,i-1))+1);  //这里注意处理当前数的情况
    }
    return ans;
}
int main()
{
    getdp();
    int cas=1,t;
    LL m;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%lld",&m);
        printf("%lld\n",cas++,getans(m));
    }
    return 0;
}


poj3252

地址:http://poj.org/problem?id=3252


题意:求“round number”,该种数的要求是将数转化为二进制后其含有的数字0多余1。

思路:跟hdu3709平衡数很像,不过平衡数是要求左右相等,而此题是要求0大于1。处理方法差不多,就是注意下在len=0时return的情况就行了。还有就是注意一下首位数为0的情况,以及当首位数不为0是往下求组合数的时候关于0的判断。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int dp[40][100][2],num[40]; //这里多开一维是因为关于0有两种情况,一个是包含(前面的数字有1),一个是不包含(前面的数字没1)
int dfs(int len,int pos,bool p,bool z) //这里z是表示是否有前面的数字有1
{
    if(!len) return pos<=50; //对于len为0时的返回值
    if(!p&&~dp[len][pos][z]) return dp[len][pos][z];
    int ans=0,u=(p?num[len]:1);
    for(int i=0;i<=u;i++)
    {
        if(!z&&!i) ans+=dfs(len-1,pos,i==u&p,0); //这里我写两个判断是强迫症,可以和在一起
        else ans+=dfs(len-1,pos+i*2-1,i==u&p,1);
    }
    if(!p) dp[len][pos][z]=ans;
    return ans;
}
int getans(int m)
{
    if(m<0) return 0;
    int len=0;
    for(;m;m/=2)
        num[++len]=m%2;
    return dfs(len,50,1,0);
}
int main()
{
    memset(dp,-1,sizeof(dp));
    int l,r;
    scanf("%d%d",&l,&r);
    printf("%d\n",getans(r)-getans(l-1));
    return 0;
}


lightoj1068

地址:http://www.lightoj.com/volume_showproblem.php?problem=1068

 

题意:能被K整除且各位数字之和也能被K整除的数。

思路:先用打表的方法,但超时了。果断换成深搜,A了。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int dp[15][110][110],num[20],k; //把k设成全局变量
int dfs(int len,int m,int n,bool p)
{
    if(!len) return m==0&n==0;
    if(!p&&~dp[len][m][n]) return dp[len][m][n];
    int ans=0,u=p?num[len]:9;
    for(int i=0;i<=u;i++)
        ans+=dfs(len-1,(m+i)%k,(i*(int)pow(10.0,len-1)+n)%k,i==u&p);
    if(!p) dp[len][m][n]=ans;
    return ans;
}
int getans(int m)
{
    int len=0;
    for(;m;m/=10)
        num[++len]=m%10;
    return dfs(len,0,0,1);
}
int main()
{
    int l,r,t,cas=1;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,-1,sizeof(dp)); //每次注意重置数组
        scanf("%d%d%d",&l,&r,&k);
        if(k>82) printf("Case %d: 0\n",cas++);
        else printf("Case %d: %d\n",cas++,getans(r)-getans(l-1));
    }
    return 0;
}

Ural1057

地址:http://acm.timus.ru/problem.aspx?space=1&num=1057
题意:找区间内可以由k个不同的b^n加和的数字个数(n可以为0,1,2……)。
思路:直接套路。提交时因为数组开小了WA了N次,注意一下,虽然数字不大于2^31-1,但是数字要转化成二进制等等,所以数字最大不止10位。
代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int dp[40][25],k,b,num[40];
int dfs(int len,int mak,bool p)
{
    if (!len) return (mak==k);
    if (mak>k) return 0;
    if (!p&&dp[len][mak]!=-1) return dp[len][mak];
    int u=p?num[len]:1;
    int ans=0;
    for(int i=0;i<=u&&i<=1;i++)
        ans+=dfs(len-1,i?mak+1:mak,p&i==u);
    if(!p) dp[len][mak]=ans;
    return ans;
}
int getans(int m)
{
    int len=0;
    for(;m;m/=b)
        num[++len]=m%b;
    return dfs(len,0,1);
}
int main()
{
    memset(dp,-1,sizeof(dp));
    int x,y;
    scanf("%d%d%d%d",&x,&y,&k,&b);
    printf("%d\n",getans(y)-getans(x-1));
}




0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:70185次
    • 积分:1908
    • 等级:
    • 排名:千里之外
    • 原创:127篇
    • 转载:0篇
    • 译文:0篇
    • 评论:2条
    最新评论