数位dp入门

数位dp,一般的题意是要求把某个区间的符合某种特征的数的个数求出来,一般的思路就是把最大数按位分解,然后dfs依次判断每一位相应的数是否满足要求。
把以前做过的几道简单数位dp的题总结一下,可以作为入门题做做。
1.
hdu 2089 不要62
题意:给定一个区间[a,b],问在这个区间中的数字,不包含4和62的数有多少个?

分析:数据范围小于1e6,如果暴力的话,再加上分解因数,预处理的时间复杂度数量级最大1e7,查询O(1)。
用数位dp的做法:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int f[8][2],bit[8];
/*
f[i][j]:i是位数
j==0,尾位不是6
j==1,尾位是6
*/
int dfs(int pos,bool six,bool lim)//位数,尾位是否是6,是否是上限
{
    if(pos<=0)return 1;
    if(!lim&&f[pos][six]!=-1)return f[pos][six];
    int num=lim?bit[pos]:9; //假设该位是2,下一位是3,如果现在算到该位为1,那么lim=0,表示下一位是能取到9的,
                            //如果该位为2,那么lim=1,下一位只能取到3
    int ans=0;
    for(int i=0;i<=num;i++){
        if(i==4||six&&i==2)continue;//如果再加一位是4,或者尾位是6再加一位是2,那么都是不合法的状态,跳过。
        ans+=dfs(pos-1,i==6,lim&&i==num);
    }
    if(!lim)f[pos][six]=ans;
    return ans;
}
int solve(int n)
{
    int len=0;
    memset(f,-1,sizeof(f));
    while(n){
        bit[++len]=n%10;
        n/=10;
    }
    return dfs(len,0,1);
}
int main()
{
    int a,b;
    while(~scanf("%d%d",&a,&b)&&(a+b)){
        printf("%d\n",solve(b)-solve(a-1));
    }
    return 0;
}

2.
hdu 3652 B-number
题意:求小于n,是13的倍数且含有’13’的数的个数。n<1e9

分析:有两个要求,不含13且不是13的倍数,这样的话,可是可以用三维数组表示状态f[i][j][k],i表示数位,然后增加上用j表示余数,k表示是否含有13。然后dfs,从高位开始以此判断每一位的状态。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N=15;
int bit[N],f[N][N][3];
/*
dp[i][j][k] j表示余数
k==0 不包含13且不以1结尾
k==1 不包含13且以1结尾
k==2 包含13
*/
int dfs(int pos,int mod,int have,bool lim)//第几位数,余数,状态标号,是否是上限
{
    if(pos<=0)return mod==0&&have==2;
    if(!lim&&f[pos][mod][have]!=-1)return f[pos][mod][have];
    int num=lim?bit[pos]:9;
    int ans=0;
    for(int i=0;i<=num;i++){
        int mod_x=(mod*10+i)%13; //传到下一位的余数,模拟除法的过程
        int have_x=have;
        if(have==0&&i==1)have_x=1;//末尾不是1,再加一位是1,标记为1
        if(have==1&&i!=1)have_x=0;//末尾是1,再加一位不是1,标记为0
        if(have==1&&i==3)have_x=2;//末尾是1,再加一位是3,标记为2
        ans+=dfs(pos-1,mod_x,have_x,lim&&i==num);
    }
    if(!lim)f[pos][mod][have]=ans;
    return ans;
}
int main()
{
    int n,len;
    while(~scanf("%d",&n)){
        memset(f,-1,sizeof(f));
        len=0;
        while(n){
            bit[++len]=n%10;
            n/=10;
        }
        printf("%d\n",dfs(len,0,0,1));
    }
    return 0;
}

3.
hdu 3555 含有49的数:
题意:
找出2^63范围内含有49的数,注意用long long

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=20;
ll f[N][3];
int bit[N];
//f[pos][have]
//0 最后一位不是4且不含49
//1 最后一位是4
//2 有49
ll dfs(int pos,int have,bool lim)
{
    if(pos<=0)return have==2;
    if(!lim&&f[pos][have]!=-1)return f[pos][have];
    int num=lim?bit[pos]:9;
    ll ans=0;
    for(int i=0;i<=num;i++){
        int have_x=have;
        if(have==1&&i==9)have_x=2;
        else if(have==0&&i==4)have_x=1;
        else if(have==1&&i!=4)have_x=0;

        ans+=dfs(pos-1,have_x,lim&i==num);
    }
    if(!lim)f[pos][have]=ans;
    return ans;
}
ll solve(ll n)
{
    memset(f,-1,sizeof(f));
    int tot=0;
    while(n){
        bit[++tot]=n%10;
        n/=10;
    }
    return dfs(tot,0,1);
}
int main()
{
    int T;scanf("%d",&T);
    while(T--){
        ll n;cin>>n;
        printf("%lld\n",solve(n));
    }
    return 0;
}

这个也可以用递推解决:

dp[k][0]=dp[k-1][0]*9+dp[k-1][1]*8;         //dp[][0]表示不包含49并且以非4结尾的个数

dp[k][1]=dp[k-1][0]+dp[k-1][1];           //dp[][1]表示不包含49并且以4结尾的个数

dp[k][2]=dp[k-1][1]+dp[k-1][2]*10;        //dp[][2]表示包含49的个数

4.
数位dp的思想是处理一个序列,一位位的分析,上面的都是先把数分解成一个序列,有的题目就是直接问哟多少种满足要求的序列,处理也是一样的,对每一个位处理即可。
例:
hdu 5642 数位dp/ 递推
题意:数一个长度为 n的序列 , 并且序列中不能出现长度大于 3 的连续的相同的字符
分析:
这道题目是BC的一道题目,大部分人是用递推解决的,因为递推特别简单。

    ll f[N][4]; //依次表示末尾应经出现连续相等字母的数量
    f[1][1]=26;
    for(int i=2;i<N;i++){
        f[i][1]=(f[i-1][1]+f[i-1][2]+f[i-1][3])*25%mod;
        f[i][2]=f[i-1][1];
        f[i][3]=f[i-1][2];
    }

这题用数位dp的思想也是可以做的,d[i][j][k]为处理完 i个字符 , 结尾字符为 ′a′+j, 结尾部分已重复出现了 k次的方案数。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2009;
int f[N][26][4];
int dfs(int pos,int m,int k)
{
    if(pos<=1)return 1;
    if(f[pos][m][k]!=-1)return f[pos][m][k];
    int ans=0;
    for(int i=0;i<26;i++){
        if(i!=m)ans=(ans+dfs(pos-1,i,1))%mod;
        else if(i==m&&k<3)ans=(ans+dfs(pos-1,i,k+1))%mod;
    }
    return f[pos][m][k]=ans;
}
int main()
{
    int T;scanf("%d",&T);
    int n;
    memset(f,-1,sizeof(f));
    while(T--){
        scanf("%d",&n);
        for(int i=0;i<26;i++)dfs(n,i,1);
        int ans=0;
        for(int i=0;i<26;i++)ans=(ans+f[n][i][1])%mod;
        printf("%d\n",ans);
    }
    return 0;
}

5.
hdu 4347
题意:给一个数A (十进制表示形式为AnAn-1An-2 … A2A1,定义函数 F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1,给一个B,求B以内的i,满足F(i)<=F(A)

分析:f[i][j]表示i位比j小的数有多少,先计算出F(a),然后dfs,找出比fa小的B以内的数,找B以内的数,这就是简单的数位dp了,先把B按位存储,然后从高位dfs一位位比较就好了。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2009;
int f[12][4600],w[12];
int bit[12];
int dfs(int pos,int sum,bool lim)
{
    if(pos<=0)return 1;
    if(!lim&&f[pos][sum]!=-1)return f[pos][sum];
    int num=lim?bit[pos]:9;
    int ans=0;
    for(int i=0;i<=num;i++){
        if(sum-i*w[pos]>=0)ans+=dfs(pos-1,sum-i*w[pos],lim&&i==num);
    }
    if(!lim)f[pos][sum]=ans;
    return ans;
}
int main()
{
    for(int i=0;i<11;i++)w[i+1]=1<<i;
    int T;scanf("%d",&T);
    memset(f,-1,sizeof(f));
    for(int cas=1;cas<=T;cas++){
        int a,b;
        scanf("%d%d",&a,&b);
        int fa=0,i=0;
        while(a){
            fa+=(a%10)*w[++i];
            a/=10;
        }
        i=0;
        while(b){
            bit[++i]=b%10;
            b/=10;
        }
        printf("Case #%d: %d\n",cas,dfs(i,fa,1));
    }
    return 0;
}

6.
题意:
d magic number(0<=d<9)的意思就是一个数,从最高位开始奇数位不是d,偶数位是d
题目问,给a,b,m,d(a<=b,m<2000)问,a,b之间有多少个数满足既是d magic number,又可以被m整除
分析:
跟上面第二题有些类似,需要注意位数最大可能是2000。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2009;
int f[N][N],bit[N];
char a[N],b[N];
int m,d,len;
int dfs(int pos,int pre,bool lim)
{
    if(pos==len+1)return pre==0;
    if(!lim&&f[pos][pre]!=-1)return f[pos][pre];
    int num=lim?bit[pos]:9;
    int ans=0;
    for(int i=0;i<=num;i++){
        if((pos&1)&&i==d)continue;
        if(!(pos&1)&&i!=d)continue;
        ans=(ans+dfs(pos+1,(pre*10+i)%m,lim&&i==num))%mod;
    }
    if(!lim)f[pos][pre]=ans;
    return ans;
}
int solve(const char *s)
{
    len=strlen(s);
    for(int i=0;i<len;i++)bit[i+1]=s[i]-'0';
    return dfs(1,0,1);
}
int judge(const char *s)
{
    len-strlen(s);
    int pre=0;
    for(int i=0;i<len;i++){
        pre=(pre*10+s[i]-'0')%m;
        if(i&1&&(s[i]-'0'!=d))return 0;
        if(!(i&1)&&s[i]-'0'==d)return 0;
    }
    return pre==0;
}
int main()
{
    //freopen("f.txt","r",stdin);
    memset(f,-1,sizeof(f));
    scanf("%d%d",&m,&d);
    scanf("%s%s",a,b);
    printf("%d\n",(solve(b)-solve(a)+judge(a)+mod)%mod);
    return 0;
}

上面的6道数位dp题可以看出形式差不多是一样的,前几道是直接搬得我以前写的题解,注释写的比较详细,希望可以帮助需要的朋友!

转载于:https://www.cnblogs.com/01world/p/5651204.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值