数位DP合集

1.HDU 4722 good numbers:

题意:给出一个区间【A,B】,求出区间内有多少个数的各位的和加起来模10等于0的数有多少个。

解法:这是一个数位DP简单入门题,简单的DFS+数组记忆化搜索就可以。姿势是自己写的。。感觉略搓,做到第三个数位DP题的时候看到了别人的姿势,学习了。

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
//#include <unordered_map>
#define N 22
using namespace std;
typedef pair<long long,long long> PII;
const long long INF=0x3f3f3f3f;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}

long long dit[N];
long long dp[N][N][3];
long long dfs(long long idx,long long mod,bool limit,long long edidx)
{
    if(dp[idx][mod][limit]!=-1)
        return dp[idx][mod][limit];
    if(idx==edidx+1)
    {
        if(mod%10==0)
        {
            //cout<<1<<endl;
            return dp[idx][mod][limit]=1;
        }
        return dp[idx][mod][limit]=0;
    }

    long long ret=0;

    for(long long i=0;i<= (limit?dit[edidx-idx]:9);i++)
    {
        ret+=dfs(idx+1,(mod+i)%10,limit && i==dit[edidx-idx],edidx);
    }
    return dp[idx][mod][limit]=ret;
}

long long getval(long long x)
{
    long long ditnum=0;
    memset(dp,-1,sizeof dp);
    while(x)
    {
        dit[ditnum++]=x%10;
        x/=10;
    }
    return dfs(1,0,1,ditnum);
}

int main()
{
    Open();
    long long T;
    scanf("%I64d",&T);
    int cas=1;
    while(T--)
    {
        long long a,b;
        scanf("%I64d%I64d",&a,&b);
        bool flag=false;
        long long ta=a;
        long long tmp=0;
        while(ta)
        {
            tmp+=(ta%10);
            ta/=10;
        }
        //cout<<tmp<<endl;
        if(tmp%10==0) flag=1;
        printf("Case #%d: %I64d\n",cas++,getval(b)-getval(a)+flag);
    }
    return 0;
}

2.HDU 3555 Bomb

题意:"49"连在一起的数是good number,现在给你一个区间,让你找出区间中有多少个good number。

解法:仍然是一个简单数位DP,将上面一题的代码随便改改就可以过了。

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
//#include <unordered_map>
#define N 22
using namespace std;
typedef pair<long long,long long> PII;
const long long INF=0x3f3f3f3f;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}

long long dit[N];
long long dp[N][3][3][3];
long long dfs(long long idx,bool ppre,long long pre,bool limit,long long edidx)
{
    if(dp[idx][ppre][pre][limit]!=-1)
        return dp[idx][ppre][pre][limit];

    if(idx==edidx+1)
    {
        if(ppre && pre == 1)
        {
            return dp[idx][ppre][pre][limit]=1;
        }
        return dp[idx][ppre][pre][limit]=0;
    }
    if(ppre && pre == 1)
    {
        long long cur=10;
        if(limit){
            int tmpidx=idx;
            cur=dit[edidx-idx];
            while(++tmpidx<=edidx)
            {
                cur*=10;
                cur+=dit[edidx-tmpidx];
            }
            cur++;
        }else{
            int tmpidx=idx;
            while(++tmpidx<=edidx)
            {
                cur*=10;
            }
        }
        return dp[idx][ppre][pre][limit]=cur;
    }

    long long ret=0;

    for(long long i=0;i <= (limit?dit[edidx-idx]:9);i++)
    {
        long long flag=0;
        if(i==4) flag=2;
        if(i==9) flag=1;
        ret+=dfs(idx+1,pre==2,flag,limit && i==dit[edidx-idx],edidx);
    }
    return dp[idx][ppre][pre][limit]=ret;
}

long long getval(long long x)
{
    long long ditnum=0;
    memset(dp,-1,sizeof dp);
    while(x)
    {
        dit[ditnum++]=x%10;
        x/=10;
    }
    return dfs(1,0,0,1,ditnum);
}

int main()
{
    Open();
    long long T;
    scanf("%I64d",&T);
    while(T--)
    {
        long long a;
        scanf("%I64d",&a);
        printf("%I64d\n",getval(a));
    }
    return 0;
}

3.HDU 2089 不要62

题意:数字中有“4”或者“62”的数字是不吉利数字,仍然是给出一个区间,让我们求出有多少个吉利的数字。

解法:仍然是简单的数位DP。改改代码就过了。。

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
//#include <unordered_map>
#define N 22
using namespace std;
typedef pair<long long,long long> PII;
const long long INF=0x3f3f3f3f;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}

long long dit[N];
long long dp[N][4][4][2];
long long dfs(long long idx,bool ppre,long long pre,bool limit,long long edidx)
{
    if(dp[idx][ppre][pre][limit]!=-1)
        return dp[idx][ppre][pre][limit];

    if(idx==edidx+1)
    {
        if((ppre && pre == 1) || pre==3)
        {
            //cout<<"1idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<1<<endl;
            return dp[idx][ppre][pre][limit]=1;
        }
        ///cout<<"2idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<0<<endl;
        return dp[idx][ppre][pre][limit]=0;
    }
    if((ppre && pre == 1) || pre==3)
    {
        long long cur=10;
        if(limit){
            int tmpidx=idx;
            cur=dit[edidx-idx];
            while(++tmpidx<=edidx)
            {
                cur*=10;
                cur+=dit[edidx-tmpidx];
            }
            cur++;
        }else{
            int tmpidx=idx;
            while(++tmpidx<=edidx)
            {
                cur*=10;
            }
        }
        //cout<<"3idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<cur<<endl;
        return dp[idx][ppre][pre][limit]=cur;
    }

    long long ret=0;

    for(long long i=0;i <= (limit?dit[edidx-idx]:9);i++)
    {
        long long flag=0;
        if(i==6) flag=2;
        if(i==2) flag=1;
        if(i==4) flag=3;
        ret+=dfs(idx+1,pre==2,flag,limit && i==dit[edidx-idx],edidx);
        //ret+=dfs(idx+1,pre,i,limit && i==dit[edidx-idx],edidx);
    }
    //cout<<"4idx,ppre,pre,limit val "<<idx<<" "<<ppre<<" "<<pre<<" "<<limit<<" "<<ret<<endl;
    return dp[idx][ppre][pre][limit]=ret;
}

long long getval(long long x)
{
    long long ditnum=0;
    memset(dp,-1,sizeof dp);
    while(x)
    {
        dit[ditnum++]=x%10;
        x/=10;
    }
    return dfs(1,0,0,1,ditnum);
}

int main()
{
    Open();
    long long n,m;
    while(~scanf("%I64d%I64d",&n,&m) && (n||m))
    {
        //cout<<a<<endl;
        printf("%I64d\n",m-n+1-getval(m)+getval(n-1));
    }
    return 0;
}

4.HDU 4734 F(x)

题意:
定义:
F(x) = A n * 2 n-1 +
n-1 * 2 n-2 +
... + A 2 * 2
+ A 1 * 1. 
 给出A,B,问0~B中有多少个数不大于F(A)。

解法:
注意到此题的数据组数非常巨大(10000组),所以我们如果再用上面两种方法的时候就会T,因为在上面的方法中,dp记忆化搜索数组将“limit”这个值也计算进
去了,也就是说,每一组数据的dp数组我们都要重新初始化,重新计算。当我观察了别的菊苣的代码之后,发现原来数位DP正确的姿势应该不是我那样的,而是
DP数组能够利用在不同的数据组中,也就是说多组数据的时候,我们也不用每次都将DP数组初始化。如此,我们的复杂度便到了可接受的地步。

回到正题,我们要求0~B中有多少个数小于F(A)的话,当然首先得将F(A)算出来,在DFS过程中,我们记录idx,sum,limit,idx表示当前是第几位,sum表示前idx位
的和,而limit的作用不再赘述,大家都懂。此时的复杂度比较小。O(idx*sum)是可以接受的。

代码:

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
//#include <unordered_map>
#define N 21
using namespace std;
typedef pair<long long,long long> PII;
const long long INF=0x3f3f3f3f;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}
long long valA;
long long dit[N];
long long dp[N][11110];
long long dfs(long long idx,long long sum,bool limit)
{
    if(sum<0) return 0;

    if(idx == -1)
    {
        return sum>=0;
    }

    if(!limit && dp[idx][sum]!=-1)
        return dp[idx][sum];

    long long ret=0;

    int ed=limit?dit[idx]:9;
    for(long long i=0;i <= ed;i++)
    {
        ret+=dfs(idx-1,sum-i*(1<<idx),limit && i==dit[idx]);
    }
    if(!limit)
        return dp[idx][sum]=ret;
    return ret;
}

long long getval(long long x)
{
    long long ditnum=0;
    while(x)
    {
        dit[ditnum++]=x%10;
        x/=10;
    }
    return dfs(ditnum-1,valA,1);
}

int main()
{
    Open();
    long long T;
    scanf("%I64d",&T);
    long long n,m;
    long long cas=1;
    memset(dp,-1,sizeof dp);
    while(T--)
    {
        scanf("%I64d%I64d",&n,&m);
        valA=0;
        int dig=0;
        while(n)
        {
            valA+=(n%10)*(1<<dig);
            dig++;
            n/=10;
        }
        printf("Case #%I64d: %I64d\n",cas++,getval(m));
    }
    return 0;
}

5.HDU 4389 X mod f(x)

题意:
还是给出一个区间【A,B】,求出区间内有多少个数x满足 X mod f(X) ==0。其中F(x)表示X十进制表示下的各位数位的数的和。

解法:
此题与前四个题目比起来难了一些。在这里DFS的方法并不好想,所以用了数位DP直推的方法写。定义dp[len][i][j][k];表示len位的数 F(x)==i 的数模 j 的结果为k的
数有多少个。这样的话,我们首先预处理出dp数组。递推方程式比较容易出的。然而问题是预处理了dp数组之后我们该怎么得到结果。

具体见代码注释:

#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <queue>
#include <algorithm>
#include <cmath>
//#include <unordered_map>
#define N 11
using namespace std;
typedef pair<int,int> PII;
const int INF=0x3f3f3f3f;
void Open()
{
    #ifndef ONLINE_JUDGE
        freopen("D:/in.txt","r",stdin);
        //freopen("D:/my.txt","w",stdout);
    #endif // ONLINE_JUDGE
}
int valA;
int dit[N];
//int dp[N][4][4][2];
int dp[N][91][91][91];//dp[len][i][j][k]前len位数和为i模j结果为k的数的个数

void init()
{
    for(int i=1;i<=81;i++)
        dp[0][0][i][0]=1;
    for(int len=0;len<9;len++)
        for(int i=0;i<=9*len;i++)
            for(int j=1;j<=81;j++)
                for(int k=0;k<j;k++)
                    for(int dig=0;dig<10;dig++){
                        dp[len+1][i+dig][j][(k*10+dig)%j] += dp[len][i][j][k];
                        //cout<<dp[len][i][j][k];
                    }
}

int ten[12]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};

int getval(int x)
{
    int ditnum = 0;
    int ans = 0;
    int tmpx = x;
    //得到x的十进制数组
    while(tmpx)
    {
        dit[ditnum++]=tmpx%10;
        tmpx /= 10;
    }

    int presum=0;
    int preNum=0;

    for(int i=ditnum-1;i>=0;i--)//遍历x的各个数位。
    {
        int ret=0;
        for(int j=0;j<dit[i]+(i==0);j++)//对小于x的每个数都进行处理
        {
            //cout<<i<<","<<j<<endl;
            for(int k=j+presum;k<=81;k++)//枚举每一个可作为mod的数。从j+presum开始,(j+presum)表示当前位之前的数的和。
            {
                if(!k)//如果k==0,表示不符合条件。
                    continue;
                int r=preNum%k; //得到前面几位数字%k说得到的余数。
                r=(k-r)%k;      //这样的(r+(preNum%k))%k==0 一定成立
                ans+=dp[i][k-j-presum][k][r];
            }
            preNum+=ten[i]; //更新preNum
            //cout<<"here:"<<preNum<<endl;
        }
        presum+=dit[i]; //更新presum
    }
    return ans;
}

int main()
{
    Open();
    int T;
    scanf("%d",&T);
    int n,m;
    int cas=1;
    init();//预处理出dp数组
    //memset(dp,-1,sizeof dp);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        printf("Case %d: %d\n",cas++,getval(m)-getval(n-1));
    }
    return 0;
}
时间: 2024-08-03 04:59:36

数位DP合集的相关文章

P2507 - 【DP合集】m-knapsack

给出 n 个物品,第 i 个物品有重量 w i .现在有 m 个背包,第 i 个背包的限重为 c i ,求最少用几个背 包能装下所有的物品. Input 输入的第一行两个整数 n, m ( n ≤ 24 , m ≤ 100) . 接下来一行 n 个整数,描述 w ( w i ≤ 10^8 ) . 接下来一行 m 个整数,描述 c ( c i ≤ 10^8 ) . Output 输出一行一个整数,描述最少需要使用的背包数.如果没有可行的方案来装下所有物品,请输出"NIE" (不含引号).

【DP合集】tree-knapsack

Description 给出一个 N 个节点的有根树,点编号 1 - N ,编号为 i 的点有权值 v i .请选出一个包含树根的,点数 不超过 K 的连通块,使得点权和最大. Input 输入的第一行有二个整数 N , K ( K ≤ N ≤ 3000) . 接下来一行 N 个整数,第 i 个数描述编号为 i 的点的父亲编号,若该数为 0 ,则表示点 i 为树根. 接下来一行 N 个整数,第 i 个数描述编号为 i 的点的权值. Output 输出一行一个整数,描述最大的点权和.保证答案不会超

【BZOJ2728】[HNOI2012]与非 并查集+数位DP

[BZOJ2728][HNOI2012]与非 Description Input 输入文件第一行是用空格隔开的四个正整数N,K,L和R,接下来的一行是N个非负整数A1,A2……AN,其含义如上所述. 100%的数据满足K≤60且N≤1000,0<=Ai<=2^k-1,0<=L<=R<=10^18 Output 仅包含一个整数,表示[L,R]内可以被计算出的数的个数 Sample Input 3 3 1 4 3 4 5 Sample Output 4 HINT 样例1中,(3

数位dp/记忆化搜索

一.引例 #1033 : 交错和 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0, a1, ..., an - 1,定义交错和函数: f(x) = a0 - a1 + a2 - ... + ( - 1)n - 1an - 1 例如: f(3214567) = 3 - 2 + 1 - 4 + 5 - 6 + 7 = 4 给定 l, r, k,求在 [l, r] 区间中,所有 f(x) = k 的 x 的和,

HDU 4507 吉哥系列故事――恨7不成妻(数位dp&amp;好魔性的一道好题)

题目链接:[kuangbin带你飞]专题十五 数位DP J - 吉哥系列故事――恨7不成妻 题意 Time Limit:500MS Memory Limit:32768KB 64bit IO Format:%I64d & %I64u Description 单身! 依然单身! 吉哥依然单身! DS级码农吉哥依然单身! 所以,他生平最恨情人节,不管是214还是77,他都讨厌! 吉哥观察了214和77这两个数,发现: 2+1+4=7 7+7=7*2 77=7*11 最终,他发现原来这一切归根到底都是

[题解+总结]动态规划大合集II

1.前言 大合集总共14道题,出自江哥之手(这就没什么好戏了),做得让人花枝乱颤.虽说大部分是NOIP难度,也有简单的几道题目,但是还是做的很辛苦,有几道题几乎没思路,下面一道道边看边分析一下. 2.lis 最长上升子序列 唯一一道裸题,但是O(n^2)过不了,临时看了看O(n log n)的二分做法和线段树做法.先来讲讲简单的二分做法,其本质就是在O(n^2)上进行优化,需要证明一个结论.设当前处理数列第k位,存在: (1)a[i]<a[j]<a[k]: (2)i<j<k: (3

[知识点]NOIP动态规划大合集

1.前言 NOIP2003-2014动态规划题目大合集,有简单的也有难的(对于我这种动态规划盲当然存在难的),今天就把这些东西归纳一下,做一个比较全面的总结,方便对动态规划有一个更深的理解. 2.NOIP2003 加分二叉树 树形DP入门题,根据题意,一个树的加分=左子树*右子树+根节点,由此可以设f[i][j]为子树i到j的加分,则有方程:f[i][j]=max{d[t]+f[i,t-1]*f[t+1,j]} ( t∈[i,j] ) 3.NOIP2004 合唱队形 应该是最简单的一道了,枚举队

2013-2014 NBA 东西部决赛 + 总决赛合集

2013-2014 NBA 东西部决赛 + 总决赛合集 2013至2014赛季,NBA职业联赛已结束,这期间有过热血,期待,感动,也有过一些失望.但不管怎样,终究又是一个伟大的赛季,缔造了很多伟大的记录,催生了数位超级球星,带给我们球迷无数的叹为观止的精彩瞬间.总决赛马刺4-1热火完成了去年7场的复仇,拿到了总冠军.莱昂纳德荣获总冠军MVP,超越魔术师约翰逊成为NBA历史上第二年轻的FMVP的获得者.詹姆斯悲情无助,憾失三连冠,这也让人猜测,三巨头下赛季能否继续一起奋战?我们只能静静等待.现特意

前端相关网址合集

综合类 | 地址--- | --- 前端知识体系--http://www.cnblogs.com/sb19871023/p/3894452.html前端知识结构--https://github.com/JacksonTian/fks免费的编程中文书籍索引--https://github.com/justjavac/free-programming-books-zh_CN智能社 - 精通JavaScript开发--http://study.163.com/course/introduction/2