数位 DP 入门习题

125 阅读2分钟

HDU 5179 beautiful number

地址

题意:求一个递减并且上一位是下一位倍数的数,比如931,在 n范围内数的个数。

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

const int N = 15;
int f[N][10]; // 表示i 位数,最高位为j 的数量

void init(){
    memset(f, 0, sizeof f);
    for(int i=1; i<=9; i++) {
        f[1][i] = 1;
    }
    for(int i=2; i<N; i++){
        for(int j=1; j<=9; j++){
            for(int k=1; k<=9; k++){
               if(j%k == 0){
                   f[i][j] += f[i-1][k];
               }
            }
        }
    }
}

int dp(int n){
    if(!n) return 0;
    vector<int> nums;
    while(n) nums.push_back(n%10), n/=10;
    int len = nums.size();
    int res = 0;
    int last = -1;
    // 等于 nums.size() 位的数量
    for(int i=nums.size()-1; i>=0; i--){
        int x = nums[i];
        for(int j=1; j < x; j++){
            // last == -1 前面没有数的情况
            if(last == -1 || last%j == 0){
                res += f[i+1][j];
            }
        }
        if(x == 0 || (last != -1 && last % x)) break;
        if(!i && last%x == 0){
            res++;
        } 
        last = x;
    }
    // 小于 nums.size() 位的数量
    for(int i=1; i<nums.size(); i++){
        for(int j=1; j<=9; j++){
            res += f[i][j];
        }
    }
    return res;
}
int main() {
    int T, l, r;
    cin>>T;
    while(T--){
        init();
        cin>>l>>r;
        cout<<dp(r)-dp(l-1)<<endl;
    }
    return 0;
}

HDU 2089 不要62

地址
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
题意:在 n 范围内的数,不包含 4 或者 62 的数字的个数。

#include <iostream>
#include <cstring>
#include <vector> 
 
using namespace std;

const int N = 15;
int f[N][N];

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++){
            for(int k=0; k<=9; k++){
                if(j == 4 || (j == 6 && k == 2)) continue;
                f[i][j] += f[i-1][k];
            }
        }
    }
}
int dp(int n){
    if(!n) return 1;
    vector<int> nums;
    while(n) nums.push_back(n%10), n/=10;
    int res = 0, last = 0;
    for(int i=nums.size()-1; i>=0; i--){
        int x = nums[i];
        for(int j=0; j<x; j++){
            if(j == 4 || (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() {
    int l, r;
    init();
    while(cin>>l>>r, l || r){
        cout<<dp(r)-dp(l-1)<<endl;
    }
    return 0;
}

HDU 3555 Bomb

地址

题意:在 n 范围内包含 "49" 的个数,代码是通过 n+1-不包含"49"的个数,包含0。

#include <iostream>
#include <cstring>
#include <vector>

using namespace std;

const int N = 25;

long long f[N][N];

void init(){
    memset(f, 0, sizeof f);
    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(j == 4 && k == 9)
                    continue;
                f[i][j] += f[i-1][k];
            }
        }
    }
}

long long dp(long long n){
    if(!n) return 1;
    vector<int> nums;
    while(n) nums.push_back(n%10), n/=10;
    long long res = 0, last = 0;
    for(int i=nums.size()-1; i>=0; i--){
        int x = nums[i];
        for(int j=0; j<x; j++){
            if(last != 4 || j != 9){
                res += f[i+1][j];
            }
        }
        if(last == 4 && x == 9) break;
        if(!i) res++;
        last = x;
    }
    return res;
}
int main() {
   long long T, n;
   cin>>T;
   while(T--){
       init();
       cin>>n;
       cout<<n+1-dp(n)<<endl;
   }
   return 0;
}

UESTC 250 windy数

地址
windy定义了一种windy数。

不含前导零且相邻两个数字之差至少为22的正整数被称为windy数。

windy想知道,在AA和BB之间,包括AA和BB,总共有多少个windy数?

题意:在 l,r 范围内的数字,上一位和下一位数字之差绝对值大于等于2的个数。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;


const int N = 25;
int f[N][N];

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;
    vector<int> nums;
    while(n) nums.push_back(n%10), n/=10;
    int res =0, last = -2;
    for(int i=nums.size()-1; i>=0; i--){
        int x = nums[i];
        // 去掉前导零
        for(int j=(i == nums.size()-1); j<x; j++){
            if(abs(last - j) >= 2){
                res += f[i+1][j];
            }
        }
        if(abs(last - x) < 2) break;
        if(!i) res++;
        last = x;
    }
    for(int i=1; i<nums.size(); i++){
        for(int j=1; j<=9; j++){
            res += f[i][j];
        }
    }
    return res;
}
int main()
{
    int l, r;
    cin>>l>>r;
    init();
    cout<<dp(r) -dp(l-1)<<endl;
    return 0;
}

HDU 3652 B-number

地址

题意:求1~n之间有多少个数字包含13且能被13整除。

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;

//dp[i][j][l][state],i位数j开头1 , (mod) 13的值 l , 是否含有13 (0/1)
int f[12][12][15][2];

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][0] = 1;
    int power = 10; // 从2位数开始
    for(int i=2;i<=10;i++) {
        for(int j=0;j<10;j++) {
            for(int k=0;k<10;k++) {
                for(int l=0;l<13;l++) {
                    f[i][j][l][1] += f[i-1][k][mod(l-j*power, 13)][1];
                    if(j == 1 && k == 3)
                        f[i][j][l][1] += f[i-1][k][mod(l-j*power, 13)][0];
                    else
                        f[i][j][l][0] += f[i-1][k][mod(l-j*power, 13)][0];
                }
            }
        }
        power *= 10;
    }
}

int dp(int n) {
    vector<int> nums;
    int power = 1;
    while(n){
      nums.push_back(n%10), n/=10;
      if(n)
        power*= 10;  
    } 
    int sum = 0;
    bool mark = false; // 前面出现过 "13"
    int last = 0;
    int res = 0;
    for(int i = nums.size()-1; i >=0; i--) {
        int x = nums[i];
        for(int j=0;j<x;j++) {
            res += f[i+1][j][mod(-sum, 13)][1]; // 出现过 "13"
            if((last == 1 && j == 3) || mark) // 当前为 "13" 或者前面的数出现过 "13"
                res += f[i+1][j][mod(-sum, 13)][0];
        }
        sum += x*power;    // 高位数字之和
        if(last == 1 && x== 3) // 当前为 "13"
            mark = true; 
        last = x;   // 记录上个位置
        power /= 10;
    }
    // 当前数 n 是否包含"13" 并且 mod 13 = 0
    if(mark && sum%13 == 0) res++;
    return res;
}

int main() {
    init();
    int n;
    while(cin>>n) {
        cout<<dp(n)<<endl;
    }
    return 0;
}