数位dp(杭电2089题)

什么是数位dp

数位dp是一种在数位上操作用于计数的dp,一般用于统计一个区间[l,r]内满足某些条件数的个数

那什么是数位呢?一个数有个位、十位、百位、千位等等,数的每一位都是数位

其实,数位dp的实质就是换一种暴力枚举的方式使得新的枚举方式满足dp的性质,然后进行记忆化就可以了。

当大家看到类似于统计一个区间[L,R]中满足某些条件数的个数时,且此时L,R都会取1e9~1e18这么大的范围时,百分之90的情况下都用数位dp!!

数位dp的思路

理解数位dp就须理解新的枚举方式:控制上界枚举,就是从最高位开始往下枚举
例如:r=213,
我们从百位开始枚举:百位上可能的情况有0,1,2,然后每一位枚举都不能让枚举的这个数超过上界213,当百位上枚举了0或1,那么十位上枚举就从0到9,因为百位上0或1比上界2小,后面的数位无论枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界时,那么紧接着的一位枚举就有上界限制了。这个例子中如果百位枚举了2,那么十位的枚举情况就只能是0到1,如果前两位枚举了21,那么最后一位是0到3
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089
代码如下:

#include<bits/stdc++.h>
using namespace std;
int dp[8][10],digit[10];  //d为存储上界的各个数位上的数
//dp[i][j]是第i位数首位是j的方法数 
void init()   //数位dp的初始化 
{
	dp[0][0]=1;  //初始化 
	for(int i=1;i<=7;i++)  //遍历位数 
	   for(int j=0;j<=9;j++)  //j为i的首位数 
	      for(int k=0;k<=9;k++)  //k为i-1的首位数 
	         if(j!=4&&!(j==6&&k==2))  //当j!=4且!(j==6&&k==2)时进行累加
	           dp[i][j]+=dp[i-1][k];  //状态转移方程 
}
int solve(int n)
{
	int ans=0,len=0;
	while(n!=0)
	{  //例如1234,存储在数组中是4321 
	   len++;
	   digit[len]=n%10;
	   n=n/10;	
	}
	digit[len+1]=0; //因为dp需要它的前一位数所以置为0 
	for(int i=len;i>=1;i--)  //从高位枚举 
	{
		for(int j=0;j<digit[i];j++)  //枚举范围内的每一个数字 
		{
			if(j!=4&&!(digit[i+1]==6&&j==2))  //如何当前位置合适,则加入方案数 
			   ans+=dp[i][j];
		}
		if(digit[i]==4||(digit[i]==2&&digit[i+1]==6))  break;  
		//第i位已经不满足条件,则i位以后都不可能满足条件,结束循环
	} 
	return ans;   
}
int main()
{
	int n,m;
	init();
	while(cin>>n>>m)
	{
		if(n==0&&m==0) break;
		cout<<solve(m+1)-solve(n)<<endl;
//solve(n)返回的是[0,n)满足条件个数,由于要取得的范围是[m,n],所以用solve(n+1)-solve(m) 
	}
	return 0;
}

刚开始可能刚学脑子有点懵,为什么从高位枚举是从len开始,因为digit数组存储数字是从个十百千万存的,例如1234存储在数组中是4321
另一个没懂的地方是在solve函数,例如40,第一遍for循环就结束了,那个位数的解决方案算进去了吗?j可以为0,所以01、02之类的算进去的,因为加了dp[len][0]的值

猜你喜欢

转载自blog.csdn.net/qq_39905917/article/details/87535011