【数位DP】

【总览】

通过将数拆分成一位位的进行dp(记忆化搜索),状态最基本的有:位置($pos$),最高位限制($limit$),前导零($lead$),前一位($pre$)等等,通常需要的状态视题目而定。

记忆化搜索的数组$dp$由多维构成,每一位都是一种状态的因素。

【bzoj】不要62

数位$dp$入门题,$dp$数组为$dp[pos][pre][limit]$($limit$可以通过更改代码去掉这一维)。

【code】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<cmath>
using namespace std;

const int N = 10;
int as[N], bs[N], lena, lenb;
long long dp[N][N][2];
int a, b;

inline int read(){
    int i = 0, f = 1; char ch = getchar();
    for(; (ch < ‘0‘ || ch > ‘9‘) && ch != ‘-‘; ch = getchar());
    if(ch == ‘-‘) f = -1, ch = getchar();
    for(; ch >= ‘0‘ && ch <= ‘9‘; ch = getchar())
        i = (i << 3) + (i << 1) + (ch - ‘0‘);
    return i * f;
}

inline void wr(long long x){
    if(x < 0)putchar(‘-‘), x = -x;
    if(x > 9) wr(x / 10);
    putchar(x%10 + ‘0‘);
}

inline long long dfs(int pos, int last, bool limit, int *s, int l){
    if(pos == l + 1) return 1;
    if(dp[pos][last][limit] != -1) return dp[pos][last][limit];
    int high = limit ? s[pos] : 9;
    long long ret = 0;
    for(int i = 0; i <= high; i++){
        if(last == 6 && i == 2) continue;
        if(i == 4) continue;
        ret += dfs(pos + 1, i, limit && (i == high), s, l);
    }
    dp[pos][last][limit] = ret;
    return ret;
}

int main(){
    while(1){
        a = read(), b = read();
        if(!a && !b) break;
        //----------------------------------
        memset(dp, -1, sizeof dp);
        memset(as, 0, sizeof as);
        lena = 0;
        if(a > b) swap(a, b);
        int tmp;
        tmp = a - 1;
        while(tmp){
            as[++lena] = tmp % 10;
            tmp /= 10;
        }
        reverse(as + 1, as + lena + 1);
        long long r1 = dfs(1, 0, 1, as, lena);
        //----------------------------------

        memset(dp, -1, sizeof dp);
        memset(bs, 0, sizeof bs);
        lenb = 0;
        tmp = b;
        while(tmp){
            bs[++lenb] = tmp % 10;
            tmp /= 10;
        }
        reverse(bs + 1, bs + lenb + 1);
        long long r2 = dfs(1, 0, 1, bs, lenb);
        //-------------------------------------
        wr(r2 - r1), putchar(‘\n‘);
    }
}

【bzoj】windy数

同样是入门级别的题,$dp$中加入前导零这一维(亦可更改代码去掉),当当前位可作为前导零时,有两种选择:

  • 继续前导零
  • 填上大于$0$的数,以后的数不能再使用“前导”$0$(普通$0$可以用)

若不能在作为前导$0$,则必须满足差值大于等于$2$的要求。

【code】

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 15;
int dp[N][N][2][2]; //位置,前一个数,最高位限制
int as[N], bs[N], lena, lenb, a, b;

inline int dfs(int pos, int last, bool limit, bool lead, int *s, int l){
    if(pos == l + 1) return 1;
    if(dp[pos][last][limit][lead] != -1) return dp[pos][last][limit][lead];
    int high = limit ? s[pos] : 9, ret = 0;
    for(int i = 0; i <= high; i++){
        if(!lead){
            if(i != 0)ret += dfs(pos + 1, i, limit && i == high, 1, s, l);
            else ret += dfs(pos + 1, i, limit && i == high, 0, s, l);
        }
        else if(abs(i - last) >= 2)
            ret += dfs(pos + 1, i, limit && i == high, 1, s, l);
    }
    dp[pos][last][limit][lead] = ret;
    return ret;
}    

int main(){
    scanf("%d%d", &a, &b);
    if(a > b) swap(a, b);
    //------------------------
    memset(dp, -1, sizeof dp);
    int tmp = a - 1;
    while(tmp) as[++lena] = tmp % 10, tmp /= 10;
    reverse(as + 1, as + lena + 1);
    int r1 = dfs(1, 0, 1, 0, as, lena);

    memset(dp, -1, sizeof dp);
    tmp = b;
    while(tmp) bs[++lenb] = tmp % 10, tmp /= 10;
    reverse(bs + 1, bs + lenb + 1);
    int r2 = dfs(1, 0, 1, 0, bs, lenb);
    cout<<r2 - r1<<endl;
    return 0;
}

【cf】Beautiful Numbers

 学到了。本题难点在于离散压空间。若正常的进行$dp$,那么数组大小早就爆了。

现在考虑这样一个问题:对于$1~9$的数而言,最小公倍数为2520. 由题知,设最后的数为$x$

  数学推到知$a \equiv b (mod p) \Leftrightarrow q | a - b$

  

时间: 2024-10-05 05:21:20

【数位DP】的相关文章

51Nod 1009 数字1的个数 | 数位DP

题意: 小于等于n的所有数中1的出现次数 分析: 数位DP 预处理dp[i][j]存 从1~以j开头的i位数中有几个1,那么转移方程为: if(j == 1) dp[i][j] = dp[i-1][9]*2+pow(10,i-1);else dp[i][j] = dp[i-1][9]+dp[i][j-1]; 然后注意下对于每个询问统计的时候如果当前位为1需要额外加上他后面所有位数的个数,就是n%pow(10,i-1); 这样总复杂度log(n)*10 #include <bits/stdc++.

HDU 3555 Bomb (数位DP)

数位dp,主要用来解决统计满足某类特殊关系或有某些特点的区间内的数的个数,它是按位来进行计数统计的,可以保存子状态,速度较快.数位dp做多了后,套路基本上都差不多,关键把要保存的状态给抽象出来,保存下来. 简介: 顾名思义,所谓的数位DP就是按照数字的个,十,百,千--位数进行的DP.数位DP的题目有着非常明显的性质: 询问[l,r]的区间内,有多少的数字满足某个性质 做法根据前缀和的思想,求出[0,l-1]和[0,r]中满足性质的数的个数,然后相减即可. 算法核心: 关于数位DP,貌似写法还是

51nod1043(数位dp)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1043 题意:中文题诶- 思路:数位dp 我们用dp[i][j]来存储长度为2*i且一半和为j的所有情况(包括前导0的情况),为了方便我们现在只讨论其一半的和的情况,因为如果包括前导0的话其两边的情况是一样的: 我们假设再长度为i-1的数字最前面加1位数字k,0<=k<=9(这位数字加在哪里并不影响答案,因为我们在计算i-1长度的时候已经计算了所有组合情况,

数位dp

1.[hdu3709]Balanced Number 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<cstdlib> 6 #include<algorithm> 7 #include<ctime> 8 #include<cmath> 9 #include<queue>

【HDU 3652】 B-number (数位DP)

B-number Problem Description A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string "13" and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task

hdu 5898 odd-even number 数位DP

odd-even number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 716    Accepted Submission(s): 385 Problem Description For a number,if the length of continuous odd digits is even and the length

LightOJ1068 Investigation(数位DP)

这题要求区间有多少个模K且各位数之和模K都等于0的数字. 注意到[1,231]这些数最大的各位数之和不会超过90左右,而如果K大于90那么模K的结果肯定不是0,因此K大于90就没有解. 考虑到数据规模,数据组数,这题状态这么表示: dp[i][j][k]:位数为i模K结果为j且各位数之和模K结果为k的数字个数 然后就是转移方程,最后就是统计.. 统计部分好棘手...半乱搞下AC的..还是对数位DP的这一部分太不熟悉了. 1 #include<cstdio> 2 #include<cstr

HDU 4734 F(x)(数位DP)

Description For a decimal number x with n digits (A nA n-1A n-2 ... A 2A 1), we define its weight as F(x) = A n * 2 n-1 + A n-1 * 2 n-2 + ... + A 2 * 2 + A1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there betwe

hdu 4734 数位dp

http://acm.hdu.edu.cn/showproblem.php?pid=4734 Problem Description For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1. Now you are given two numbers A and B, plea

【POJ3208】传说中POJ最难的数位DP?(正解AC自动机,二解数位DP,吾异与之)

题意: 多组数据,每组求第n个包含'666'的数(不能断开),如1:666,2:1666,14:6667. 题解: AC自动机解法没去想,数位DP没学,这里有一种类似于数位DP,却又与数位DP不同,我称为数位树. 数位树: 将数n如线段树一样地拆分成多个小段,进行递归处理得出答案. 本题详(lue)解: 直接看每一位应该是什么数,然后n减去相应的数,使得在下一层转换为子问题"在开头有b个连续的6时,求第a个带'666'的数".就是如此简单,如此简单!!!! 代码来啦! #include