数位dp学习

发布于:2024-08-08 ⋅ 阅读:(77) ⋅ 点赞:(0)

参考借鉴:

数位DP学习整理(数位DP看完这篇你就会了)-CSDN博客

AcWing1081.度的数量(数位DP)题解_求给定区间$ [x,y]$ 中满足下列条件的整数个数:这个数恰好等于 k k k 个互不相等-CSDN博客

 

就是类似前缀和的思想,进行数字在位数上的前缀操作,以此dp来简化暴力计算的方法;

例题一:

求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的 B的整数次幂之和。例如,设 X = 15 , Y = 20 , K = 2 , B = 2 ,则有且仅有下列三个数满足题意:
17=2^{4}+2^{0}

18=2^{4}+2^{1}

20=2^{4}+2^{2}
输入格式
第一行包含两个整数 X 和 Y,接下来两行包含整数 K和 B。
输出格式
只包含一个整数,表示满足条件的数的个数。
数据范围
1 ≤ X ≤ Y ≤ 2^{31}-1
1 ≤ K ≤ 20
2 ≤ B ≤ 10
输入样例:
15 20
2
2
输出样例:
3

 解题思路:
首先将N转化为b进制数,则一个数能由b的整数次方合成,则转化为b进制后,对应的b的次方的位置为1, 那么我们只需判断转化为b进制后,各个位数上0,1的个数就可以,得到总的方案数;

假设 N 转化为 b 进制后有 n 位数,从最高位开始遍历,当前位的数为 x, 枚举填 0 和填 1 的方案:

1.如果 x == 0,直接跳过(不能由b的多少次方组成)

2.如果x == 1, 当前位置 i 可以填0, 还需要填 k 个 1 ,则需要从剩下的 i - 1 位中填 k 个 1 ,即C(i - 1, k), 并让x =1,继续判断下一位;

3.如果x > 1, 则该位上可以填 0 ,方案数为C(i - 1, k), 也可以填 1 ,即方案数为C(i - 1, k - 1), 因为填大于 1 的数不合法,所以直接break掉

4.判断 i 是否等于 0,即判断i是否为最后一位, 如果是的话, 那么最后以后可以填 0 , ans++;f(i, j)表示从i个数里面选j个数的方案数,预处理出每一位上左分支的方案数

//首先我们先预处理f数组。其中f[i][j]表示剩下还有i个没填,需要填写j个1的方案数。
void init(){
    for(int i=0;i<35;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];
            }
        }
    }
}

上述代码描述了填写内容的转移关系

 完整代码:

#include <bits/stdc++.h>
#include <iostream>  
#include <vector>  
#include <string>  
#include <unordered_set>  
#include <cmath>  

using namespace std;

typedef long long ll;
const int maxn = 100000 + 5;
const int mod = 1e9+7;

int l=15,r=20,k=2,b=2;
int f[35][35];
//首先我们先预处理f数组。其中f[i][j]表示剩下还有i个没填,需要填写j个1的方案数。
void init(){
    for(int i=0;i<35;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];
            }
        }
    }
}
int dp(int n){
    //求解f(n)。我们需要避免n为0的情况,这里需要特判。
    if(!n)return 0;
    vector<int> nums;//将n分割,存储位数。
    while(n){
        nums.push_back(n%b);
        n/=b;
    }
    int ans=0;//答案。
    int last=0;//前面的信息,这里代表的是前面分支选取了多少个1.
    for(int i=nums.size()-1;i>=0;i--){
        int x=nums[i];
        if(x){
            //说明x>0,我们可以选择左边分支填0.
            ans+=f[i][k-last];
            if(x>1){
                //当x>1我们才可以枚举左边分支填1.
                if(k-last-1>=0){
                    //如果还可以填1的话。
                    ans+=f[i][k-last-1];
                }
                break;//因为右边分支只能为0或1,所以不符合条件。break。
            }
            else{
                //当x=1就可以进入右边的分支继续讨论。
                last++;
                if(last>k)break;
            }
        }
        //考虑到最后一位,如果符合条件那么末位填0也算一种方案。
        if(!i&&last==k)ans++;
    }
    return ans;
}
void solve(){
}
int main(){
    // cin>>l>>r>>k>>b;
    int l=15,r=20,k=2,b=2;
    init();
    cout<<dp(r)-dp(l-1)<<endl;
    solve();
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到