数位dp入门

前些天做牛客多校碰到一个题目,题解要用到数位dp,OTZ,不会该怎么办,学什么,愣着啊。

所谓数位dp统计每一位数字的情况,统计的过程中一定会遇到各种重复,那么我们利用记忆化的方法来避免这些重复计算就是我们今天要学的数位dp。

话不多说,我们先从一个小例子来说,

这个题意很简单就是不要49。让你统计[l,r]区间内没有出现49的数有几个。比如4,9,94,123,1419,这些数都没出现49,而49,149,499,4499,14499这些数都不符合要求。

那么如何计数,暴力?n=1000000000000000000,怕是要跑到你脱单也跑不完。什么你有女朋友了(iДi)那别搞了,好好陪女朋友多好。(羡慕脸

回归正题,我们可以利用前缀的思想,写个程序统计1到任意m中满足条件的数,那么答案就是solve(r)-solve(l-1)。这个应该都没啥问题。然后把我们要统计的m分解成数字存数组里,然后len表示他的位数,就是下面这个程序。

ll solve(ll n)
{
	int k=0;
        while(n)
	{
		a[++k]=n%10;n/=10;
	}
	return dfs(k,0,1);
}

前戏做完了,下面进入正题。dp肯定是有状态的,所以这个状态怎么表示就成了解决问题的关键。首先数字的长度肯定是一状态,所以我们在dfs中加一个参数len表示当前统计的是a数组中第几位数。当然还有一个比较重要的状态就是上一个数字是不是4,如果上一个数字是4,那么我们当前数字肯定不能是9,为什么?(题意要求的)。然后我们还要知道当前数字是不是可以超过a[len],就是所谓的当前数字是不是受限制的。比如670当统计到第1位6时,我们不能让第一位超过6,因为如果第一位超过6,anemia整体数字就超过699,就大于m了,再举个栗子,671,当统计到第二位时,它是由上一位时5dfs下来的,所以没有限制他可以从0取到9,那么总体数字也不会大于599.

还不懂的话就看代码吧。解释的很清楚。

//数位dp不含49的数。 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[100][2];
int a[100];
//len:当前统计到第几位
//if4:上一位是否是4
//limit:当前的数是否有上限
ll flag=0;
ll dfs(int len,bool if4,bool limit)
{flag++;
	if(len==0) return 1;
	if(!limit&&dp[len][if4]) return dp[len][if4];		//数位dp的本质就是记忆化减少重复计算。 
	//只有没有限制时才能返回,如果有限制,不同的限制的结果也会不一样。 
	int maxi=limit?a[len]:9;
	ll cnt=0;
	for(int i=0;i<=maxi;i++)
	{
		if(if4&&i==9) continue;
		if(i==4) cnt+=dfs(len-1,1,limit&&i==maxi);
		else cnt+=dfs(len-1,0,limit&&i==maxi);
	    //只有之前有限制现在的达到了上限才能构成限制
        //什么是上限,比如数字756,当统计到第一位且i=7时,那么此时dfs统计下一位就有了上限,因为第二位最多只能到5,因为再多就超过了756。
        //再比如当统计到第一位且i=6时,那么此时dfs统计下一位就没有上限,因为第二位任意取总体都不会超过699。
    }
	if(!limit)			//如果有限制,那么就不能记忆化
	dp[len][if4]=cnt;
	return cnt;
}
ll solve(ll n)
{
	int k=0;while(n)
	{
		a[++k]=n%10;n/=10;
	}
	return dfs(k,0,1);
}
int main()
{
	ll l,r;cin>>l>>r;
	cout<<solve(r)-solve(l-1)<<endl;
cout<<flag<<endl;	return 0;
} 

写完这题那么再来一题,不要62和4。理解完上一题的你这题应该对你来说很轻松了。建议先自己写一下。

//数位dp不含62和4的数。 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[100][2];
int a[100];
//len:当前统计到第几位
//if6:上一位是否是6
//limit:当前的数是否有上限
ll dfs(int len,bool if6,int limit)
{
	if(len==0) return 1;
	if(!limit&&dp[len][if6]) return dp[len][if6];
	int maxi=limit?a[len]:9;
	ll cnt=0;
	for(int i=0;i<=maxi;i++)
	{
		if(i==4) continue;		//碰到4就跳过啦 
		if(i==2&&if6) continue;	//碰到62也跳过 
		cnt+=dfs(len-1,i==6,limit&&i==maxi);
	}
	if(!limit) dp[len][if6]=cnt;
	return cnt;
}
ll solve(ll n)
{
	int k=0;while(n)
	{
		a[++k]=n%10;n/=10;
	}
	return dfs(k,0,1);
}
int main()
{
	ll l,r;cin>>l>>r;
	cout<<solve(r)-solve(l-1)<<endl;
	return 0;
} 

再来一题BZOJ1026

//数位dp相邻数字差不小于2 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[100][200];
int a[100];
//len:当前统计到第几位
//up:上一位 
//limit:当前的数是否有上限

ll dfs(int len,int up,int limit)
{
	if(len==0) return 1;
	if(!limit&&dp[len][up]) return dp[len][up];
	int maxi=limit?a[len]:9;
	ll cnt=0;
	for(int i=0;i<=maxi;i++)
	{
		if(abs(i-up)>=2)
		{
			int p=i;
			if(up==100&&i==0)p=up;
			cnt+=dfs(len-1,p,limit&&i==maxi);
		}
	}
	if(!limit) dp[len][up]=cnt;
	return cnt;
}
ll solve(ll n)
{
	int k=0;while(n)
	{
		a[++k]=n%10;n/=10;
	}
	return dfs(k,100,1);
}
int main()
{
	ll l,r;cin>>l>>r;
	cout<<solve(r)-solve(l-1)<<endl;
	return 0;
} 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值