其他分享
首页 > 其他分享> > 数位DP

数位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

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


标签:last,nums,int,res,个数,++,DP,数位
来源: https://www.cnblogs.com/liang302/p/16298081.html