20230401数位DP

数位DP

数位DP通常指在\([l,r]\)区间中有多少个满足条件\(k\)的个树
常见的数据范围都很大

也就是说,
把数字的枚举,变成数字的构造
不要把数字看作是\(10^{18}\)
而把数字看作是\(18\)位数的填数过程
就是把原本枚举的问题转化为了构造的问题

然而数位dp常通过记忆化搜索实现

tips:

1.求区间\([l,r]\)的问题也要常转化为\([1,r]-[1,l-1]\)
2.在枚举前我们需要考虑好枚举的方向,也就是从低到高或从高到低

例题

P8801 [蓝桥杯 2022 国 B] 最大数字

题目
这是一个很典型的数位dp
对于每一位数字
我们从高位到低位枚举其可能变成的数字
而在高位的数字越大越好
所以对于每一位数,我们从\(9\)开始枚举
在这一位第一次递推成功后就break
因为再往下枚举就没有更优解了

//当前枚举到第id位,a,b,当前的答案位number 
void dfs(int id,int a,int b,ll number){
  //1.出口
  if(id==0){
  	ans=max(ans,number);
  	return ;
  }
  //2.能做的事情 
  for(int i=9;i>=0;i--){
  	int acost=(i-num[id]+10)%10;
  	int bcost=(num[id]-i+10)%10;
  	bool flag=false;
    if(a>=acost){
      flag=true;
      dfs(id-1,a-acost,b,number*10+i);
	}
	if(b>=bcost){
	  flag=true;
	  dfs(id-1,a,b-bcost,number*10+i);
	}
	if(flag) break;
  }
}
P8764 [蓝桥杯 2021 国 BC] 二进制问题

题目
同样对于每一位要枚举
但要加上对上界的考虑
枚举不能超过\(n\)
(注意记忆化搜索数组的大小,不要开小了)

//当前枚举到第id位,前面有num个1,这一位是否有限制
ll dfs(int id,int num,int limit){
  //1.出口
  if(id==0||num==k) return num==k;
  if(dp[id][num][limit]>=0) return dp[id][num][limit];
  //2.能做的事情
  ll res=0;
  int bound= limit==1?a[id]:1;
  for(int i=0;i<=bound;i++)
  	res+=dfs(id-1,num+i,limit&&i==bound);
  return dp[id][num][limit]=res;
} 
P4999 烦人的数学作业

题目
和前面一道题没有什么大的区别
但要注意减法取模时要\((a-b+mod) \mod mod\)
而由于多组数据,记忆化数组\(dp\)其实是可以重复使用的
对于每位数,如果后面没有限制,那么\(dp[id][sum]\)是不变的
所以不必每次清空\(dp\)

//当前枚举到第id位,前面数字和为sum,是否达到上界limit
ll dfs(int id,ll sum,int limit){
  //1.出口
  if(id==0) return sum;
  if(!limit&&dp[id][sum]>=0) return dp[id][sum];
  //2.能做的事情 
  ll res=0;
  int bound= limit==1 ? a[id] : 9;
  for(int i=0;i<=bound;i++){
  	res=(res+dfs(id-1,sum+i,limit&&i==bound))%mod;
  }
  if(!limit) dp[id][sum]=res%mod;
  return res%mod;
} 
其他题目

P2657 [SCOI2009] windy 数
P2602 [ZJOI2010] 数字计数
P4798 [CEOI2015 Day1] 卡尔文球锦标赛
P3281 [SCOI2013]数数
P2518 [HAOI2010]计数
P3286 [SCOI2014]方伯伯的商场之旅

posted @ 2023-04-08 23:32  H_W_Y  阅读(34)  评论(0)    收藏  举报