ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

数位DP

2022-05-22 16:00:40  阅读:141  来源: 互联网

标签:last nums int res 个数 ++ 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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有