1.8、数位DP(算法提高课)

一、数字游戏

题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1588

题意:求给定区间【a,b】中的不降数的个数,不降数的定义为从左到右各位数字成小于等于的关系。

思路:首先预处理出来 f[i][j] 为一共有i位,且最高位为j的数的个数,然后用数位dp求解即可,具体看代码

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 15;

int f[N][N];//f[i][j]表示一共有i位,且最高位填j的数的个数

void init(){
    for(int i=0;i<=9;i++)f[1][i]=1;

    for(int i=2;i<N;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 n){
    if(!n)return 1;//特判n为0的情况

    vector<int>v;
    while(n)v.push_back(n%10),n/=10;//把n的每一位分解出来

    int res=0,last=0;//res记录答案个数,last记录上一位的值
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        for(int j=last;j<x;j++)res+=f[i+1][j];//尝试遍历将该位填last~x-1的数能得到的数的个数

        if(x<last)break;
        last=x;

        if(!i)res++;//说明走到最后了,还有一个合法解

    }
    return res;
}
int main()
{
    init();

    int l,r;
    while(cin>>l>>r)cout<<dp(r)-dp(l-1)<<"\n";//前缀和的思想
    return 0;
}

二、windy 数

题目链接:https://www.luogu.com.cn/problem/P2657

题意:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。windy 想知道,在 ab 之间,包括 ab ,总共有多少个 windy 数?

思路:与上一题基本一样,具体看代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 15;

int f[N][10];//f[i][j]表示一共有i位,且最高位填j的数的个数

void init(){
    for(int i=0;i<=9;i++)f[1][i]=1;

    for(int i=2;i<N;i++){
        for(int j=0;j<=9;j++){
            for(int k=0;k<=9;k++){
                if(abs(j-k)>=2)
                    f[i][j]+=f[i-1][k];
            }
        }
    }
}
int dp(int n){
    if(!n)return 0;//特判n为0的情况

    vector<int>v;
    while(n)v.push_back(n%10),n/=10;//把n的每一位分解出来

    int res=0,last=-2;//res记录答案个数,last记录上一位的值
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        //注意判断不能有前导0,所以如果是第一位的话要从1开始遍历,否则从0开始遍历
        for(int j=(i==v.size()-1);j<x;j++){//尝试遍历将该位填last~x-1的数能得到的数的个数
            if(abs(j-last)>=2)
                res+=f[i+1][j];
        }

        if(abs(x-last)>=2)last=x;
        else break;

        if(!i)res++;//说明走到最后了,还有一个合法解
    }

    //特殊处理有前导0的数
    for(int i=1;i<v.size();i++){
        for(int j=1;j<=9;j++){
            res+=f[i][j];
        }
    }
    return res;
}
int main()
{
    init();

    int l,r;cin>>l>>r;
    cout<<dp(r)-dp(l-1)<<"\n";//前缀和的思想
    return 0;
}

三、数字游戏II

题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1588

题意:由于科协里最近真的很流行数字游戏,某人又命名了一种取模数,这种数字必须满足各位数字之和 mod N为 0。现在大家又要玩游戏了,指定一个整数闭区间 [a,b],问这个区间内有多少个取模数。

思路:基本都是一样,根据题意改变一下预处理的数组即可

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 15 ,M=110;

int P;
int f[N][10][M];//f[i][j][k]表示一共有i位,最高位为j,且i位数字的总和为k的数的个数

int mod(int x,int y){
    return (x%y+y)%y;
}
void init(){
    memset(f,0,sizeof f);

    for(int i=0;i<=9;i++)f[1][i][i%P]++;

    for(int i=2;i<N;i++){
        for(int j=0;j<=9;j++){
            for(int k=0;k<P;k++){
                for(int x=0;x<=9;x++){
                    f[i][j][k]+=f[i-1][x][mod(k-j,P)];
                }
            }
        }
    }
}
int dp(int n){
    if(!n)return 1;//特判n为0的情况

    vector<int>v;
    while(n)v.push_back(n%10),n/=10;//把n的每一位分解出来

    int res=0,last=0;//res记录答案个数,last记录上一位的值
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        for(int j=0;j<x;j++){
            res+=f[i+1][j][mod(-last,P)];
        }

        last+=x;

        if(!i&&last%P==0)res++;//说明走到最后了,还有一个合法解
    }

    return res;
}
int main()
{
    int l,r;
    while(cin>>l>>r>>P){
        init();
        cout<<dp(r)-dp(l-1)<<"\n";//前缀和的思想
    }
    return 0;
}

四、不要62

题目链接:https://vjudge.net/problem/HDU-2089

题意:求给定区间中的数是好数的个数,好数定义为数中没有4或者62的数。

思路:与上面一样,根据题意预处理数组即可

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 15 ,M=110;

int f[N][10];//f[i][j]表示一共有i位,最高位为j满足题意得数的个数

int mod(int x,int y){
    return (x%y+y)%y;
}
void init(){
    for(int i=0;i<=9;i++){
        if(i!=4)
            f[1][i]=1;
    }

    for(int i=2;i<N;i++){
        for(int j=0;j<=9;j++){
            if(j==4)continue;
            for(int k=0;k<=9;k++){
                if(k==4||j==6&&k==2)continue;
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int dp(int n){
    if(!n)return 1;//特判n为0的情况

    vector<int>v;
    while(n)v.push_back(n%10),n/=10;//把n的每一位分解出来

    int res=0,last=0;//res记录答案个数,last记录上一位的值
    for(int i=v.size()-1;i>=0;i--){
        int x=v[i];
        for(int j=0;j<x;j++){
            if(j==4)continue;
            if(last==6&&j==2)continue;
            res+=f[i+1][j];
        }

        if(x==4||last==6&&x==2)break;

        last=x;
        if(!i)res++;
    }

    return res;
}
int main()
{
    init();
    int l,r;
    while(cin>>l>>r,l||r)cout<<dp(r)-dp(l-1)<<"\n";
    return 0;
}

五、恨7不成妻(待补)

题目链接:吉哥系列故事——恨7不成妻 - HDU 4507 - Virtual Judge (vjudge.net)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值