数位dp(dfs版)

题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=3555

题意:给定一个上限n,求1--n中存在多少个数,数中含有49

题解:dfs数位dp,此题一共三种状态:st:0:前一个数为4的状态,此位加9就可满足题意;1:前面数字中已经含有49;2:前面中没有49并且前一位不是4

状态转移:

状态49其他数字
0012
1111
2022

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
long long dp[25][5];
int num[25];//分解输入上限
long long dfs(int pos,int st,bool f)//表示dp[pos][st]为pos位st状态,f代表pos位是否受限,即表示可以取0-9任意数字还是规定了最大值
{
    if(pos<=0) return (!f&&st==1)?1:0;//只有在st为满足题意得状态时才返回1,此题为1状态时  后面原理同此
    if(!f && dp[pos][st]!=-1) return dp[pos][st];
    int limit=f?num[pos]:9;//若受限就用分解的最大值来规定最大值
    long long ans=0;
    for(int i=0;i<=limit;i++)
    {
        bool next_f =f & (limit==i);//是否受限以及是否达到受限最大值
        if(i==4)
        {
            if(st==0) ans+=dfs(pos-1,0,next_f);
            if(st==1) ans+=dfs(pos-1,1,next_f);
            if(st==2) ans+=dfs(pos-1,0,next_f);
        }
        else if(i==9)
        {
            if(st==0) ans+=dfs(pos-1,1,next_f);
            if(st==1) ans+=dfs(pos-1,1,next_f);
            if(st==2) ans+=dfs(pos-1,2,next_f);
        }
        else
        {
            if(st==0) ans+=dfs(pos-1,2,next_f);
            if(st==1) ans+=dfs(pos-1,1,next_f);
            if(st==2) ans+=dfs(pos-1,2,next_f);
        }
    }
    if(!f) dp[pos][st]=ans;//若不受限就是统计了后面位数的数字和,可以记录
    return ans;
}
long long solve(long long d)
{
    int pos=1;
    while(d)
    {
        num[pos++]=d%10;d/=10;
    }
    return dfs(pos-1,2,1);//注意要从2状态开始
}
int main()
{
    memset(dp,-1,sizeof(dp));
    int t;
    scanf("%d",&t);
    while(t--)
    {
        long long right;
        scanf("%lld",&right);
        printf("%lld\n",solve(right+1));
    }
    return 0;
}

题目: http://acm.split.hdu.edu.cn/showproblem.php?pid=2089

题意:给定上限和下限,求在这范围内不含62和4的数字个数

题解:dfs数位dp,状态:st:0:满足含有62或4的数字的个数;1:不含62和4中前一位数字是6,+2就变成满足题意;2:不含62和4并且前一位不是6,状态同上一题

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
long long dp[25][5];
int num[25];
long long dfs(int pos,int st,bool f)
{
    if(pos<=0) return (!f&&st==0)?1:0;//同意从满足题意得状态返回1
    if(!f && dp[pos][st]!=-1) return dp[pos][st];
    int limit=f?num[pos]:9;
    long long ans=0;
    for(int i=0;i<=limit;i++)
    {
        bool next_f =f & (limit==i);
        if(i==4)
        {
            if(st==0) ans+=dfs(pos-1,0,next_f);
            if(st==1) ans+=dfs(pos-1,0,next_f);
            if(st==2) ans+=dfs(pos-1,0,next_f);
        }
        else if(i==6)
        {
            if(st==0) ans+=dfs(pos-1,0,next_f);
            if(st==1) ans+=dfs(pos-1,1,next_f);
            if(st==2) ans+=dfs(pos-1,1,next_f);
        }
        else if(i==2)
        {
            if(st==0) ans+=dfs(pos-1,0,next_f);
            if(st==1) ans+=dfs(pos-1,0,next_f);
            if(st==2) ans+=dfs(pos-1,2,next_f);
        }
        else
        {
            if(st==0) ans+=dfs(pos-1,0,next_f);
            if(st==1) ans+=dfs(pos-1,2,next_f);
            if(st==2) ans+=dfs(pos-1,2,next_f);
        }
    }
    if(!f) dp[pos][st]=ans;
    return ans;
}
long long solve(long long d)
{
    int pos=1;
    while(d)
    {
        num[pos++]=d%10;d/=10;
    }
    return dfs(pos-1,2,1);
}
int main()
{
    memset(dp,-1,sizeof(dp));
    long long left,right;
    while(~scanf("%lld %lld",&left,&right)&&left+right!=0)
    {
        printf("%lld\n",right-left+1-solve(right+1)+solve(left));
    }
    return 0;
}


题目:http://acm.split.hdu.edu.cn/showproblem.php?pid=5898

题意:给定上下限,求在范围区间内满足连续奇数位为偶数并且连续偶数位为奇数的数字

题解:dfs数位dp,st:0:奇奇;1:奇偶;2:偶奇;3:偶偶;4:前导0(前一位为0)

状态转移:

状态\加值01、3、5、7、9(奇数)2、4、6、8(偶数)
0x2x
1303
2101
31x1
4401

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>

using namespace std;
long long dp[25][6];
/*
* 奇奇  0
* 奇偶  1
* 偶奇  2
* 偶偶  3
* 前导0 4
*/
int num[25];
long long dfs(int pos,int st,bool f)
{
    if(pos<=0) return (!f&&(st==1||st==2))?1:0;//1、2满足题意,所以只有他们返回0,原理同上
    if(!f && dp[pos][st]!=-1) return dp[pos][st];
    int limit=f?num[pos]:9;
    long long ans=0;
    for(int i=0;i<=limit;i++)
    {
        bool next_f =f & (limit==i);
        if(i==0)
        {
            if(st==0) ans+=0;
            if(st==1) ans+=dfs(pos-1,3,next_f);
            if(st==2) ans+=dfs(pos-1,1,next_f);
            if(st==3) ans+=dfs(pos-1,1,next_f);
            if(st==4) ans+=dfs(pos-1,4,next_f);
        }
        else if(i & 1)
        {
            if(st==0) ans+=dfs(pos-1,2,next_f);
            if(st==1) ans+=dfs(pos-1,0,next_f);
            if(st==2) ans+=dfs(pos-1,0,next_f);
            if(st==3) ans+=0;
            if(st==4) ans+=dfs(pos-1,0,next_f);
        }
        else
        {
            if(st==0) ans+=0;
            if(st==1) ans+=dfs(pos-1,3,next_f);
            if(st==2) ans+=dfs(pos-1,1,next_f);
            if(st==3) ans+=dfs(pos-1,1,next_f);
            if(st==4) ans+=dfs(pos-1,1,next_f);
        }
    }
    if(!f) dp[pos][st]=ans;
    return ans;
}
long long solve(long long d)
{
    int pos=1;
    while(d)
    {
        num[pos++]=d%10;d/=10;
    }
    return dfs(pos-1,4,1);
}
int main()
{
    memset(dp,-1,sizeof(dp));
    int t;
    scanf("%d",&t);
    long long left,right;
    for(int i=1;i<=t;i++)
    {

        scanf("%lld %lld",&left,&right);
        printf("Case #%d: %lld\n",i,solve(right+1)-solve(left));
    }
    return 0;
}

可见,模板如下:

long long dp[25][6];
int num[25];
long long dfs(int pos,int st,bool f) //pos 当前位数,st当前状态,f是否受限
{
    if(pos<=0) return (!f&&(st==xxxx))?1:0;//满足题意的状态返回1,其他返回0
    if(!f && dp[pos][st]!=-1) return dp[pos][st];//记忆化,之前计算过的不用再计算
    int limit=f?num[pos]:9;
    long long ans=0;
    for(int i=0;i<=limit;i++)
    {
        bool next_f =f & (limit==i);//判断下一位是否受限
        if(i==.....)  //枚举状态转移条件,列出状态转移方程
        {
            ..........
        }
    }
    if(!f) dp[pos][st]=ans;//记忆化
    return ans;
}
long long solve(long long d) //位数分解
{
    int pos=1;
    while(d)
    {
        num[pos++]=d%10;d/=10;
    }
    return dfs(pos-1,4,1);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值