1587 例题3 [SCOI2009] Windy 数(Bzoj1026 LOJ LUOGU2657 提高+/省选-) 需考虑前导0的数位DP

总目录

在线测评地址(ybt)

在线测评地址(LOJ)

在线测评地址(LUOGU)

需考虑前导0的数位DP

以下内容,可以结合后续的AC代码进行阅读。

dp[pos][pre]代表的是什么意思?

pos表示当前遍历的是第几位,pre表示前一位是几
     
dp[pos][pre]就是记录了遍历第pos位时,前一位为pre时的状态数
     
举例子
假设数5762,那么数位有4位
数位数组是这样存储的2 6 7 5
数组从0位开始,
所以是 0位 到 3位
那么当pos为2的时候,前一位(即第3位)有0 - 5这些情况
那么dp[2][0-5]分别存储了dp[2][0],dp[2][1]。。。。等等 这些情况

记搜过程
从起点向下搜索,到最底层得到方案数,一层一层向上返回答案并累加,最后从搜索起点得到最终答案。

对于[l,r]区间问题,我们一般把他转化为两次数位dp,即找[0,r]和[0,l−1]两段,再将结果相减就得到了我们需要的[l,r]

状态设计
如果理解了上述过程,我们需要考虑的就是怎样判断现在在哪一层,怎样判断当前的状态——这就需要我们传进一些参量。

dfs函数需要哪些参量?

首先是数位dp基本的量数字位数pos,最高位限制limit

由于数位dp解决的大多是数字组成问题,所以经常要比较当前位和前一位或前几位的关系(根据题意而定),所以一般在dfs()中也要记录前一位或前几位数pre方便比较。

除此之外还可以传进更多参量以区分状态,视题意而定。

最高位标记limit
我们知道在搜索的数位搜索范围可能发生变化;

举个例子:我们在搜索[0,567]的数时,显然最高位搜索范围是0~5,而后面的位数的取值范围会根据上一位发生变化:

当最高位是1~4时,第二位取值为[0,9];
当最高位是5时,第二位取值为[0,6](再往上取就超出右端点范围了)
为了分清这两种情况,我们引入了limit标记:

若当前位limit=1而且已经取到了能取到的最高位时,下一位limit=1;
若当前位limit=1但是没有取到能取到的最高位时,下一位limit=0;
若当前位limit=0时,下一位limit=0。
我们设这一位的标记为limit,这一位能取到的最大值为res,则下一位的标记就是i==res&&limit(i枚举这一位填的数)

dp值的记录和使用
最后我们考虑dp数组下标记录的值

本文介绍数位dp是在记忆化搜索的框架下进行的,每当找到一种情况我们就可以这种情况记录下来,等到搜到后面遇到相同的情况时直接使用当前记录的值。

dp数组的下标表示的是一种状态,只要当前的状态和之前搜过的某个状态完全一样,我们就可以直接返回原来已经记录下来的dp值。

再举个例子

假如我们找[0,123456]中符合某些条件的数

假如当我们搜到1000??时,dfs从下返上来的数值就是当前位是第1位,前一位是0时的方案种数,搜完这位会向上,这是我们可以记录一下:当前位第1位,前一位是0时,有这么多种方案种数

当我们继续搜到1010??时,我们发现当前状态又是搜到了第1位,并且上一位也是0,这与我们之前记录的情况相同,这样我们就可以不继续向下搜,直接把上次的dp值返回就行了。

注意,我们返回的dp值必须和当前处于完全一样的状态,这就是为什么dp数组下标要记录pos,pre等参量了。

最重要的来了————————————————————

接着上面的例子,范围[0,123456]

如果我们搜到了1234??,我们能不能直接返回之前记录的:当前第1位,前一位是4时的dp值?

答案是否定的

我们发现,这个状态的dp值被记录时,当前位也就是第1位的取值是[0,9],而这次当前位的取值是[0,5],方案数一定比之前记录的dp值要小。

当前位的取值范围为什么会和原来不一样呢?

如果你联想到了之前所讲的知识,你会发现:现在的limit=1,最高位有取值的限制。

因此我们可以得到一个结论:当limit=1时,不能记录和取用dp值!


没有限制的情况占多数,所以只记录没有高位限制的情况
if(!limit)
{
    dp[pos][pre] = ans;
}

有limit=1限制的怎dp[pos][pre]么办呢?每次都重新算。

前导0标记lead

由于我们要搜的数可能很长,所以我们的直接最高位搜起

举个例子:假如我们要从[0,1000]找任意相邻两数相等的数

显然111,222,888等等是符合题意的数

但是我们发现右端点1000是四位数

因此我们搜索的起点是0000,而三位数的记录都是0111,0222,0888等等

而这种情况下如果我们直接找相邻位相等则0000符合题意而0111,0222,0888都不符合题意了

所以我们要加一个前导0标记

  1. 如果当前位lead=1而且当前位也是0,那么当前位也是前导0,pos-1继续搜;
  2. 如果当前位lead=1但当前位不是0,则本位作为当前数的最高位,pos-1继续搜;(注意这次根据题意st或其他参数可能发生变化)

当然前导0有时候是不需要判断的,上述的例子是一个有关数字结构上的性质,0会影响数字的结构,所以必须判断前导0;而如果我们研究的是数字的组成(例如这个数字有多少个111之类的问题),0并不影响我们的判断,这样就不需要前导0标记了。总之,这个因题而异,并不是必须要标记(当然记了肯定是不会出错的)

类似上述的分析过程,我们也可以得出:当lead=1时,也不能记录和取用dp值!

if(!limit&&!lead) dp[pos][pre]=ans;

前导0是无效的不用管,比如说0001含前导0就可以直接视作1,1001就不含前导0

ybt

通过

测试点结果内存时间
测试点1答案正确604KB1MS
测试点2答案正确596KB2MS
测试点3答案正确604KB2MS
测试点4答案正确600KB1MS
测试点5答案正确596KB2MS
测试点6答案正确600KB1MS
测试点7答案正确604KB1MS
测试点8答案正确604KB1MS
测试点9答案正确596KB2MS
测试点10答案正确604KB1MS

LOJ

LUOGU

 AC代码如下:

#include <bits/stdc++.h>
using namespace std;
int num[12],dp[12][12];
int dfs(int pos,int pre,int limit,int lead){
	int ans=0,i,up;
	if(pos==-1)return 1;
	if(!limit&&dp[pos][pre]!=-1&&!lead)return dp[pos][pre];
	up=limit?num[pos]:9;
	for(i=0;i<=up;i++){
		if(lead){//有前导0不受限制 
			ans+=dfs(pos-1,i,limit&&i==up,lead&&i==0);
		}else if(i-pre>=2||i-pre<=-2)//无前导0受限 
			ans+=dfs(pos-1,i,limit&&i==up,lead&&i==0);
	}
	if(!limit&&!lead)dp[pos][pre]=ans;
	return ans;
}
int solve(int x){
	int pos=0;
	while(x){
		num[pos++]=x%10;
		x/=10;
	}
	return dfs(pos-1,-1,1,1);
}
int main(){
	int lt,rt;
	scanf("%d%d",&lt,&rt);
	memset(dp,-1,sizeof(dp));
	printf("%d\n",solve(rt)-solve(lt-1));
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值