加载中...

数位DP

1.前缀和思想
2.树的角度 分类 二进制当前位的an 分成 0~an-1 与 an
把最高位划分为一个个集合
左树是枚举1~a(n-1)的情况下 进入右分支的方法就是让nums[i]的i++
3.预处理

模板

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];

        ...
    }
    
    return res;

}

度的数量 数位dp+组合数+进制 预处理 求在k进制下满足的b个1 只能是0和1的情况

https://www.acwing.com/activity/content/problem/content/1308/

#include<bits/stdc++.h>
using namespace std;
const int N = 35; //位数

int f[N][N];// f[a][b]表示从a个数中选b个数的方案数,即组合数

int K, B; //K是能用的1的个数,B是B进制

//求组合数:预处理
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];
}



 //求区间[0,n]中的 “满足条件的数” 的个数
 //“满足条件的数”是指:一个数的B进制表示,其中有K位是1、其他位全是0!!!!
int dp(int n){
//特判n==0
    if(n == 0) return 0; //如果上界n是0,直接就是0种

    vector<int> nums; //存放n在B进制下的每一位
    //把n在B进制下的每一位单独拿出来
    while(n) nums.push_back( n% B) , n/= B;

    int res = 0;//答案:[0,n]中共有多少个合法的数

    //last在数位dp中存的是:右边分支往下走的时候保存前面的信息 
    //遍历当前位的时候,记录之前那些位已经占用多少个1,那么当前还能用的1的个数就是K-last
    int last = 0; 

    //从最高位开始遍历每一位
    for(int i = nums.size()-1; i>= 0; i--){

        int x = nums[i]; //取当前位上的数,表示最高的数字

        if(x>=1){ //只有当前位的数字 x>0的时候才可以讨论左右分支 x==0就直接到了下一位了 


            //x等于0的情况
            res += f[i][ K -last];//为1的情况 i个数中选K-last个数的组合数是多少,选出来这些位填1,其他位填0 


            if(x > 1){//可以枚举1的情况属于左子树
                //当前位填1,从剩下的所有位(共有i位)中选K-last-1个数。
                //对应于:左分支中填1的情况,合法
               if(K - last -1 >= 0) res += f[i][K -last -1];//i个数中选K-last-1个数填1的组合数是多少
               //对应于:左分支中其他情况(填大于1的数)和此时右分支的情况(右侧此时也>1),不合法!!!直接break。
                break;//因为x不可能取得0/1之外的数 所以直接退出 而取得0/1的情况已经被上面给自己算出
           }
            //对应于:右分支为原数字x的情况,即限定值为1的情况,也就是左分支只能取0
            //此时的处理是,直接放到下一位来处理
            //只不过下一位可使用的1的个数会少1,体现在代码上是last+1

            else {// x==1的情况下 还有下面的 记得last++
                last ++;
                //如果已经填的个数last > 需要填的个数K,不合法break
                if(last > K) break;
            }

        }
        
        
        // 这里能到达最后一个树说明前面没有被break 说明这里嘴一个数一定是0
        if(i==0 && last == K) res++; // 由于上面的 ,最右侧树,这里处理最后一个数字的操作一定要有 但是每一道题具体的判断不同
    }

    return res;
}

int main(){
    init();
    int l,r;
    cin >>  l >> r >> K >>B;
    cout<< dp(r) - dp(l-1) <<endl;  
}



数字游戏+dp预处理 要求各位数字不下降https://www.acwing.com/problem/content/1084/

左分支 满足情况的数

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

using namespace std;

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;

    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        for (int j = last; j < x; j ++ )//这里的是题目的要求 要求递增 last~x-1
            res += f[i + 1][j];//这里的值按照dp的要求 因为i是数组下标 所以i+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) << endl;

    return 0;
}

数位dp 要求相邻的两位数字之差至少为2 有前导0操作

https://www.acwing.com/activity/content/problem/content/1310/


const int N = 11;

int f[N][10];

void init()
{
    for (int i = 0; i <= 9; i ++ ) f[1][i] = 1;
 // f[i, j]表示一共有i位,且最高位填j的数的windy的个数
    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;
    int last = -2;
    for (int i = nums.size() - 1; i >= 0; i -- )//从高位开始遍历 算的是n位数里面满足条件的个数 
    {
        int x = nums[i];
        for (int j = i == nums.size() - 1; j < x; j ++ )//如果不是最高位 可以从0开始取 否则只能从1开始
            if (abs(j - last) >= 2)
                res += f[i + 1][j];//且最高位取j的方案数

        if (abs(x - last) >= 2) last = x;//同上一题 这里作为右侧的情况需要好好看看 是否满足情况 否则右侧全部的情况的都是虚的
        else break;

        if (!i) res ++ ;//最后一个情况一定满足
    }

    // 特殊处理有前导零的数(位数不足nums.sz()位的情况) 如果这里不写 那么所有位数少于这个数的数都会没有算上
//这里因为有前导0要求  02算上了 而上面的计算时候2因为是从第1位数字所以不算是那种数字 这里就是为计算这种情况
    for (int i = 1; i <= nums.size()-1; i ++ )//枚举位数
        for (int j = 1; j <= 9; j ++ )//枚举最高位取到的数
            res += f[i][j];//最多有0~size-1位数字 且最高位取j的方案数

    return res;
}

int main()
{
    init();

    int l, r;
    cin >> l >> r;
    cout << dp(r) - dp(l - 1) << endl;

    return 0;
}


数字游戏 数字和取模为0

https://www.acwing.com/activity/content/code/content/127379/
https://cdn.acwing.com/media/article/image/2021/06/14/52559_203260cecc-屏幕截图-2021-06-12-161401.png
https://cdn.acwing.com/media/article/image/2021/06/14/52559_203260cecc-%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE-2021-06-12-161401.png

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

using namespace std;

const int N = 11, M = 110;

int P;
int f[N][10][M];

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] ++ ;//f[i][j][k]共有一位 最高位是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)];//x是下一位 ([]+j)%N==K s所以[]=k-j
}

int dp(int n)
{
    if (!n) return 1;

    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    int last = 0;
    for (int i = nums.size() - 1; i >= 0; i -- )
    {
        int x = nums[i];
        for (int j = 0; j < x; j ++ )//小于x的时候
            res += f[i + 1][j][mod(-last, P)];//找到对应的值相加 (【】+last)%p==0 -->[]=0-last 

        last += x;//last存各位数的和 这里是填x的时候 进入下一个

        if (!i && last % P == 0) res ++ ;//到达最后一步 并且各位数之和为0的时候 因为枚举到个位的时候 前面<a[i]的情况计算了 所以剩下就是这个n的个位数
    }

    return res;
}

int main()
{
    int l, r;
    while (cin >> l >> r >> P)
    {
        init();

        cout << dp(r) - dp(l - 1) << endl;
    }

    return 0;
}

不要62 要求数字不能有 4 不能有连着的62

https://www.acwing.com/activity/content/problem/content/1312/

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

using namespace std;

const int N = 35;

int f[N][10];

void init()
{
    for (int i = 0; i <= 9; i ++ )
        if (i != 4)
            f[1][i] = 1;//不等4就可以了

    for (int i = 1; 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;

    vector<int> nums;
    while (n) nums.push_back(n % 10), n /= 10;

    int res = 0;
    int 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()
{
    init();

    int l, r;
    while (cin >> l >> r, l || r)
    {
        cout << dp(r) - dp(l - 1) << endl;
    }

    return 0;
}


posted @ 2022-05-22 15:54  liang302  阅读(32)  评论(0编辑  收藏  举报