数位DP练习

度的数量

题目链接:度的数量
分析:其实感觉数位DP这类题还是有点章法的,对于求a~b中满足一个条件的数的个数,我们可以转化为求0/1 ~b中满足的个数和0/1 ~a-1中满足的个数,前者减去后者就是答案,而求0/1 ~n中满足条件的个数时我们可以根据题目的要求进行分类讨论,最常见的就是按位讨论了。我们把n按题目要求的进制数进行每一位的拆分并存储在数组中,我们按找从高位到低位进行讨论,这里我们会用到组合数,提前处理一下就可以了。
代码实现:

#include <iostream>
#include <vector>
using namespace std;
const int N = 35;
int K, B;
int f[N][N];//f[i][j]表示从i个数中选择j个数的方案数
void init(){
    for (int i = 0; i < N; i ++ )//处理组合数
        for (int j = 0; j <= i; j ++ )
            if (!j) f[i][j] = 1;
            else f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
}
int dp(int n){
    vector<int> nums;
    while (n) nums.push_back(n % B), n /= B;//按B进制拆分并存储起来
    int res = 0;
    int last = 0;//last记录之前的那些位已经放置了多少个1
    for (int i = nums.size() - 1; i >= 0; i -- ){
        int x = nums[i];
        if (x) {//如果此位上不是0才讨论,否则就直接继续下一位
            res += f[i][K - last];//当前位填0,那么就在剩下的i位中选K-last个填1
            if (x > 1){//如果x>1,并且这一位填1,从剩下的位中选k-last-1个1,这样总方案数就确定了
                if (K - last - 1 >= 0) res += f[i][K - last - 1];
                break;
            }
            else{//x为1的话,last++
                last ++ ;
                if (last > K) break;//如果1的数量超过要求了,就break
            }
        }
        if (!i && last == K) res ++ ;//如果考虑到了最后一位并且1的个数正好够,说明这个数本身就是一个符合条件的   
    }
    return res;
}
int main(){
    init();
    int l, r;
    cin >> l >> r >> K >> B;
    cout << dp(r) - dp(l - 1) << endl;
    return 0;
}

数字游戏

题目链接:数字游戏
分析:和上题一样,我们求出0/1~a-1之间的不降数,再求出0/1 ~b之间的不降数,相减就能得到答案。现在我们如果要求0/1 ~n之间的不降数,从最高位开始分类讨论,策略还是按位进行分类讨论。我们要预处理出来一个数组,这样才做的更快,用dp[i][j]表示以j开头i位数的不降数的个数。
代码实现:

#include<iostream>
#include<vector>
using namespace std;
int a,b,f[15][15];
void init(){
    for(int i=0;i<10;i++) f[1][i]=1;
    for(int i=2;i<15;i++)
        for(int j=0;j<=9;j++)
            for(int k=j;k<=9;k++)
                f[i][j]+=f[i-1][k];
}
int dp(int x){
    if(!x) return 1;//若x=0,则只有这一种方案
    vector<int> nums;
    while(x) nums.push_back(x%10),x/=10;
    int ans=0;
    int last=0;//记录上一位的值
    for(int i=nums.size()-1;i>=0;i--){
        int x=nums[i];
        for(int j=last;j<x;j++){//因为要不降,所以从上一位的数开始枚举
            ans+=f[i+1][j];
        }
        if(last>x)//如果last>x的话,说明后面的就不成立了
            break;
        last=x;
        if(!i)ans++;//若枚举到最后一位还没退出,说明这个数本身也是一个答案。
    }
    return ans;
}
int main(){
    init();
    while(cin>>a>>b){
        cout<<dp(b)-dp(a-1)<<endl;    
    }
    return 0;
}

Windy数

题目链接:Windy数
分析:和上题差不多,分类讨论,然后需要预处理出来一个f数组,f[i][j]表示共i位且最高位为j的数字。
代码实现:

#include<iostream>
#include<vector>
using namespace std;
const int N=11;
int a,b,f[N][N];
void init(){
    for(int i=0;i<10;i++) f[1][i]=1;
    for(int i=2;i<=10;i++)
        for(int j=0;j<10;j++)
            for(int k=0;k<10;k++)//枚举第i-1位
                if(abs(j-k)>=2)  f[i][j]+=f[i-1][k];
}
int dp(int n){
    int res=0,last=-5;//last记录上一位的数字,初始化为一个很小的负数
    vector<int> num;
    while(n) num.push_back(n%10),n/=10;
    //先计算正好num.size()位的数字符合要求的个数
    for(int i=num.size()-1;i>=0;i--){//考虑每一位
        int x=num[i];
        for(int j=(i==num.size()-1);j<x;j++)//如果是最高位就从1开始枚举
            if(abs(j-last)>=2)
                res+=f[i+1][j];
        if(abs(x-last)<2) break;//如果两位之差小于2,就不用枚举下去了
        last=x;
        if(!i) res++;//枚举到最后一位的话说明原数字也是一个满足的数
    }
    for(int i=1;i<num.size();i++)//处理有1~num.size()-1位的符合要求的数
        for(int j=1;j<=9;j++)
            res+=f[i][j];
    return res;
}
int main(){
    init();
    cin>>a>>b;
    cout<<dp(b)-dp(a-1)<<endl;
    return 0;
}

数字游戏2

题目链接:数字游戏2
分析:毫无疑问,这题我们也要初始化一个数组,不过这题要求比较特殊,那我们就用f[i][j][k]表示i位、以j开头的、所有位数字之和mod N结果为k的数的个数。接下来都和前面的差不多。
代码实现:

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N=12,M=102;
int a,b,c,f[N][N][M];//f[i][j][k]表示i位数、以j开头、且mod c的余数为k的所有数的个数
int mod(int x,int y){
    return (x%y+y)%y;
}
void init(){
    memset(f,0,sizeof f);
    for(int i=0;i<10;i++) f[1][i][i%c]++;//一位数
    for(int i=2;i<12;i++)//枚举位数
        for(int j=0;j<=9;j++)//枚举这一位的数
            for(int k=0;k<c;k++)//枚举余数
                for(int x=0;x<=9;x++)//枚举上一位的数
                    f[i][j][k]+=f[i-1][x][mod(k-j,c)];//(k-j)%c不一定是正数,要处理一下
}
int dp(int n){
    if(!n) return 1;
    int ans=0,last=0;//last记录前面所有数的各位数字之和
    vector<int> num;
    while(n) num.push_back(n%10),n/=10;
    for(int i=num.size()-1;i>=0;i--){
        int x=num[i];
        for(int j=0;j<x;j++){//枚举这一位的数
            ans+=f[i+1][j][mod(-last,c)];//-last%c的正余数
        }
        last+=x;
        if(!i&&last%c==0) ans++;//如果枚举到最后一位并且last是c的倍数,说明次数本身就是一个答案
    }
    return ans;
}
int main(){
    while(cin>>a>>b>>c){
        init();
        cout<<dp(b)-dp(a-1)<<endl;
    }
    return 0;
}

不要62

题目链接:不要62
分析:和前面的又有些区别了,我们用f[i][j]表示填到第i位,第i位填j时合适的车牌数,从最低位往最高位填,最低为是第1位。剩下的和前面的题差不多,细节方面稍有不同。
代码实现:

#include<iostream>
#include<vector>
using namespace std;
const int N=12;
int n,m,f[N][N];
void init(){
    for(int i=0;i<10;i++) f[1][i]=1;
    f[1][4]=0;//4去掉
    for(int i=2;i<=10;i++){
        for(int j=0;j<=9;j++){//枚举上一位
            for(int k=0;k<=9;k++){//枚举这一位
                if(k==4||j==4) continue;//这一位或者上一位为4就跳过
                if(j==6&&k==2) continue;//这一位为6且低一位的数为2也跳过
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int dp(int n){
    if(!n)return 1;
    vector<int> num;
    while(n) num.push_back(n%10),n/=10;
    int ans=0,last=0;
    for(int i=num.size()-1;i>=0;i--){
        int x=num[i];
        for(int j=0;j<x;j++){
            if(j==4) continue;//不能是4
            if(last==6&&j==2) continue;//更高位为6且较低位为2也跳过
            ans+=f[i+1][j];
        }
        if(x==4||x==2&&last==6) break;//如果这一位是2且更高位是6或者这一位是4,就退出
        last=x;
        if(!i) ans++;//枚举到了最后一位,说明这个数本身也符合题意
    }
    return ans;
}
int main(){
    init();
    while(cin>>n>>m,n&&m){
        cout<<dp(m)-dp(n-1)<<endl;
    }
    return 0;
}

还有一道我感觉比较恶心的题:恨7不成妻
有兴趣的大佬可以试试(感觉好恶心)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_bxzzy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值