hdu 3555 Bomb(数位DP)

经过这个题目对数位DP有了一个新的理解,对于本题而言,求1 ~ n中含 49 的数字个数,比如求解1 ~ 581,数位DP是可以这样来理解的,首先将数字581拆分,三位数分别是5、8、1,也就是说百位不大于5,十位不大于8,个位不大于1,但是如果百位小于5,十位和个位就没有限制,所以可以将区间1 ~ 581拆开,分成1 ~ 499(百位数字小于5)、500 ~ 579(百位为5,十位数字小于8)、580(百位为5,十位是8,个位小于1)和581分成四个部分,以次计算每一个区间求和即可。

用dp数组定义状态,dp[ i ][ j ]表示长度为 i 状态为 j 的数字个数,状态 j 有三个,分别是0、1、2,分别表示不含49的全部数字、不含49但是最高位为9的全部数字、含49的全部数字

转移方程很容易理解

dp[i][0] = dp[i - 1][0] * 10 - dp[i - 1][1];

长度为 i 不含49的全部数字可以由长度 i - 1不含49的数字转变而来,只需要在最高位前面随便填一个0 ~ 9的数字即可,但是可能会有一种情况出现49,就是我们填了一个4,而原来的最高位是9,因此应该减去dp[i - 1][1]

dp[i][1] = dp[i - 1][0];

长度为 i 最高位是9且不含49的状态可以从长度为 i - 1 不含49的任何一个数字转变过来,只需要在最高位添加一个9即可

dp[i][2] = dp[i - 1][2] * 10 + dp[i - 1][1];

对于长度为 i 含49的数字个数,首先可以是长度为 i - 1 含49的状态转移过来,在最高位随便添加一个数都行,因此需要乘以10,其次,对于最高位已经是9的状态,可以添加一个4转移过来,因此加上dp[i - 1][1]

我们将这个数组预处理后方便后面计算

在计算的时候,按照数字的位数,一位一位进行计算,从最高位开始

res 初始值为0 ,标记 f 为 false

首先最高位的长度数字为a,那么不超过它的数字个数就有0 ~ a - 1 一共a个,显然最高位随便写0 ~ a - 1之间的任何一个数字,对于dp[i - 1][2]而言都可以,所以res += dp[i - 1][2] * a;

如果当前出现过连续的两个数字一前一后分别是4、9 (对应标记 f 的值是 true) ,那么对于我们后面需要遍历的每一位而言,随便填写一个数字都可以,而且后面不一定需要再出现49,所以res += dp[i - 1][0] * a;

如果没有出现过,并且当前位最大没有限制的值大于4,说明对于长度 i - 1而言,只要最高位是9,在前面添加一个4即可,所以res += dp[i - 1][1];(因为如果前面的条件成立,那么当前的值就已经被计算进去了,dp[i - 1][0]包含dp[i - 1][1],不必重复计算)

如果当前出现了一前一后两个数分别是4、9,就将标记设为 true

最后输出 res 即可

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
ll t, n;
ll dp[30][4], a[33];
void csh(){
	dp[0][0] = 1;
	for (int i = 1; i <= 25; i++){
		dp[i][0] = dp[i - 1][0] * 10 - dp[i - 1][1];
		dp[i][1] = dp[i - 1][0];
		dp[i][2] = dp[i - 1][2] * 10 + dp[i - 1][1];
	}
}

int getlen(ll num){
	memset(a, 0, sizeof a);
	int len = 0;
	while (num){
		a[++len] = num % 10;
		num /= 10;
	}
	return len;
}
int main()
{
	csh();
	scanf("%lld", &t);
	while (t--){
		scanf("%lld", &n);
		ll res = 0;
		int len = getlen(n + 1);
		bool f = 0;
		for (int i = len; i >= 1; i--){
			res += dp[i - 1][2] * a[i];
			if(f)res += dp[i - 1][0] * a[i];
			else if(a[i] > 4){
				res += dp[i - 1][1];
			}
			if(a[i] == 9 && a[i + 1] == 4)f = 1;
		}
		printf("%lld\n", res);
	}
	return 0;
}

 

posted @ 2019-08-03 21:44  correct  阅读(86)  评论(0)    收藏  举报