蓝桥杯——数位DP

  • 参考视频
    强烈推荐
  • 什么是数位dp
    数位dp是一种特殊的动态规划,常用备忘录法(dfs+记录)来处理数位dp的问题。数位dp就是暴力的枚举整数的每一位,在枚举的过程中用dp[i][j]记录枚举过的可能,从而避免重复
  • 数位dp能解决的问题
    数位dp题型单一,一般来说数位dp是用于求解求取区间[l,r]中满足某个条件P(i)的数字的个数,l和r很大,一般为数十亿。基本上看到上述条件就说明该题是数位dp
  • 数位dp基本思路
    数位dp的模板都是固定的,所有题目都可以用同一套模板,只需要改一下数字就可以了。为了方便起见,我们常用备忘录法处理数位dp。处理步骤如下
    1. 将区间[l,r]划分为[1,l-1][1,r]。此时求解[l,r]中满足条件的数字个数就是求解[0,r]-[0,l-1]
    2. 从最高位开始枚举数字i的每一位,用采用深度优先遍历,将[0,r][0,l-1] 中所有的数字列举出来,并记录满足条件的数字个数
    3. 记忆优化,在2中暴力枚举每一个数,在枚举的过程中有些组合是重复的,对于这些重复的组合我们可以使用dp来存储。
    4. 利用23求取[1,r][1,l-1]直接相减得到最终结果
  • 数位dp常见问题
    1. 数位dp一般情况下数据空间很大不能用数组存储状态,一般使用嵌套哈希表存储状态即map<int,map<int,int>>
    2. 对r和l-1进行枚举时,在枚举前要初始化状态
  • 核心代码
    数位dp的核心代码是dfs。dfs是通用的每个数位dp的问题都能用同一套dfs,只要改一下参数就好。
    数位dp的状态一般是dfs中除了bool类型外的所有参数。具体情况,具体分析,但状态肯定都是dfs中的参数。
  • 模板
    P6218 [USACO06NOV] Round Numbers S
#include <iostream> 
#include <algorithm>
#include <bits/stdc++.h>
#include <map>
#include <queue>
using namespace std;
int n;
int l;
int r;
//a为l-1的二进制 
vector<int> a;
//b为r的二进制 
vector<int> b;
map<int,map<int,map<int,int>>> dp;
void init(int x,vector<int> &m){
	//给定x
	while(x!=0){
		m.push_back(x%2);
		x=x>>1;
	}
} 
//数位dp常用dfs解决 
int dfs(int t,int pos,int num0,int num1,bool flag,bool lead,vector<int> &roundNum){
	//pos表示位数,num0表示0个数,num1表示1个数,flag表示前面是否全部到顶
	//lead表示是否为前缀0
	if(pos<0){
		if(num0>=num1) 	return 1;
		else			return 0;
	}
	//dp[pos][num0][num1]表示在插入第pos位时,前面pos+1~MAX位的0和1数分别为num0和num1时最终所能构成的round number数
	//当flag为false时0~pos位的取值都是0和1
	if(!flag&&dp[pos][num0][num1]!=-1) return dp[pos][num0][num1];
	//确定当前位的填充上限 
	int maxNum=0; 
	if(flag)	maxNum=roundNum[pos];
	else		maxNum=1;
	int ret=0;
	for(int i=0;i<=maxNum;i++){
		if(i==0){
			//前导0直接略去 
			if(lead){
				ret+=dfs(t,pos-1,num0,num1,flag&&(i==maxNum),true,roundNum);
			}else{
				//当前0不是前导0
				num0++;
				ret+=dfs(t,pos-1,num0,num1,flag&&(i==maxNum),false,roundNum);
				num0--;
			} 	
		}else{
			num1++;
			ret+=dfs(t,pos-1,num0,num1,flag&&(i==maxNum),false,roundNum);
			num1--;
		}
	}
	if(!flag){
		dp[pos][num0][num1]=ret;
	} 
	return ret;
}
void initDP(int m){
	map<int,int> m1;
	for(int num1=0;num1<=m;num1++){
		m1.emplace(num1,-1);		
	}
	map<int,map<int,int>> m2;
	for(int num0=0;num0<=m;num0++){
			m2.emplace(num0,m1);
	}
	//初始化dp 
	for(int pos=0;pos<m;pos++){
	 	dp.emplace(pos,m2);	
	}
}
int main(){
	cin>>l>>r;
	//dfs(1)表示[1,r],dfs(0)表示[1,l-1]
	init(l-1,a);
	init(r,b);
	initDP(b.size());
	int rRes= dfs(1,b.size()-1,0,0,true,true,b);
	initDP(a.size());
	int lRes= dfs(0,a.size()-1,0,0,true,true,a);
	//状态初始化 
	cout<<rRes-lRes<<endl;
	return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值