数位DP Ⅰ

我的使用的数位DP与一般的DFS实现的不同,首先数位DP的核心思想看这张树图。从数Num的高位到低位处理,首先由于第一位最大是an-1,枚举第一位是\(0\)~\(a\)\(n-1\)\(-1\)时后面的位数都不受限制,此时我们可以预处理一个数组f[i][j],表示以j开始长度为i(也可能是组合数或者其它)的满足条件数的个数(或某种值),左边枚举的可以直接用f数组求,然后当前位固定为\(a\)\(n-1\),再去处理下一位...直到固定的某一个数不符合条件退出,如果能够遍历到最后一位,如果可以再把Num算上。

本篇的题是数位DP求区间满足条件的个数。

1081. 度的数量

求给定区间 [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

即B进制下表示有k个1其它为0,我们可以预处理一个数组f[i][j]表示长度为i中选择j位的算法

#include<bits/stdc++.h>
using namespace std;
const int N=40;
int f[N][N],k,b;
int dp(int num){
    vector<int> nums;
    while(num) nums.push_back(num%b),num/=b;
    int res=0,last=0;
    for(int i=nums.size()-1;i>=0;--i){
        int x=nums[i];
        if(x) { //x>=1 
            res+=f[i][k-last]; //填0
            if(x>1){
                res+=f[i][k-last-1]; //填1
                break; //x大于1 后面的不满足都条件
            }
            else {
                last++; //填1并走到下一位
                if(last>k) break;
            }
        }
        if(last==k&&i==0) res++;
    }
    return res;
}
int main(){
    for(int i=0;i<N;++i){
        for(int j=0;j<=i;++j){
            if(j==0) f[i][j]=1;
            else f[i][j]=f[i-1][j]+f[i-1][j-1];
        }
    }
    int l,r;
    cin>>l>>r>>k>>b;
    cout<<dp(r)-dp(l-1)<<endl;
    return 0;
}

1082. 数字游戏

科协里最近很流行数字游戏。
某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。
现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
输入格式
输入包含多组测试数据。
每组数据占一行,包含两个整数 a 和 b。
输出格式
每行给出一组测试数据的答案,即 [a,b] 之间有多少不降数。
数据范围
1≤a≤b≤231−1
输入样例:
1 9
1 19
输出样例:
9
18

预处理f数组,f[i][j]表示长度为i,第一个数为j的非降序数列个数

#include<bits/stdc++.h>
using namespace std;
const int N=15;
int f[N][N]; //f[i][j] 长度位i,第一个数字是j
int dp(int num){
    if(num==0) return 1;
    vector<int> nums;
    while(num) nums.push_back(num%10),num/=10;
    int res=0,last=0;
    for(int i=nums.size()-1;i>=0;--i){
        int x=nums[i];
        for(int j=last;j<x;++j){
            res+=f[i+1][j];
        }
        if(last>x) break;
        last=x;
        if(i==0) res++;
    }
    return res;
}
int main(){
    for(int i=0;i<10;++i) f[1][i]=1;
    for(int i=2;i<N;++i){
        for(int j=0;j<10;++j){
            for(int k=j;k<10;++k){
                    f[i][j]+=f[i-1][k];
            }
        }
    }
    int l,r;
    while(cin>>l>>r)  cout<<dp(r)-dp(l-1)<<endl;
}

1085. 不要62

杭州人称那些傻乎乎粘嗒嗒的人为 62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有 4 或 62 的号码。例如:62315,73418,88914 都属于不吉利号码。但是,61152 虽然含有 6 和 2,但不是 连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照号区间 [n,m],推断出交管局今后又要实际上给多少辆新的士车上牌照了。
输入格式
输入包含多组测试数据,每组数据占一行。
每组数据包含一个整数对 n 和 m。
当输入一行为“0 0”时,表示输入结束。
输出格式
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
数据范围
1≤n≤m≤109
输入样例:
1 100
0 0
输出样例:
80

f[i][j]表示长度为i,第一个数字为j的不包含62和4的数列个数

#include<bits/stdc++.h>
using namespace std;
const int N=30;
int f[N][N];
int dp(int num){
    if(num==0) return 1;
    vector<int> nums;
    while(num) nums.push_back(num%10),num/=10;
    int res=0,last=0;
    for(int i=nums.size()-1;i>=0;--i){
        int x=nums[i];
        for(int j=0;j<x;++j){
            if(last==6&&j==2) continue;
            res+=f[i+1][j];
        }
        if((last==6&&x==2)||x==4) break;
        last=x;
        if(i==0) res++;
    }
    return res;
}
int main(){
    for(int i=0;i<10;++i) f[1][i]=1;
    f[1][4]=0;
    for(int i=2;i<N;++i){
        for(int j=0;j<10;++j){
            if(j==4) continue;
            for(int k=0;k<10;++k){
                if(k==4) continue;
                if(j==6&&k==2) continue;
                f[i][j]+=f[i-1][k];
            }
        }
    }
    //cout<<f[2][0]<<endl;
    int l,r;
    while(cin>>l>>r,!(!l&&!r))  cout<<dp(r)-dp(l-1)<<endl;
    return 0;
}

1083. Windy数

Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为 2 的正整数被称为 Windy 数。
Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 Windy 数?
输入格式
共一行,包含两个整数 A 和 B。
输出格式
输出一个整数,表示答案。
数据范围
1≤A≤B≤2×109
输入样例1:
1 10
输出样例1:
9
输入样例2:
25 50
输出样例2:
20

f[i][j]表示长度为i,第一个数字为j的Windy数列个数。注意其他题对于枚举前导0没有限制,例如00014不是windy数,14是windy数,所以在枚举最高位是0时分情况:统计第一位为0的情况+第二位为0的情况...+0的情况

#include<bits/stdc++.h>
using namespace std;
const int N=40;
int f[N][N]; //f[i][j] 长度为i,第一个数字为j
int dp(int num){
    if(num==0) return 1;
    vector<int> nums;
    while(num) nums.push_back(num%10),num/=10;
    int res=0,last=100;
    for(int i=nums.size()-1;i>=0;--i){
        int x=nums[i];
        for(int j=0;j<x;++j){
            if(j==0&&(int)nums.size()-1==i){
                for(int l=0;l<(int)nums.size();++l){
                    for(int k=1;k<10;++k) res+=f[l][k];
                }
                res++;
            }
            else if(abs(j-last)>=2) res+=f[i+1][j];
        }
        if(abs(last-x)>=2) last=x;
        else break;
        if(i==0) res++;
    }
    //cout<<res<<endl;
    return res;
}
int main(){
    for(int i=0;i<10;++i) f[1][i]=1;
    for(int i=2;i<N;++i){
        for(int j=0;j<10;++j){
            for(int k=0;k<10;++k){
                if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
            }
        }
    }
    //cout<<f[2][0]<<endl;
    int l,r;
    cin>>l>>r;
    cout<<dp(r)-dp(l-1)<<endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/jjl0229/p/12659395.html
今日推荐