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