HDU 5179 beautiful number

传送门

数位dp。(要考虑前导0,类似还有POJ 3252)

定义一种数,要求在其十进制数位上,较高位都>=较低位,且较高位%较低位都是0(较高位都能较低位整除)。问你[L,R]内有多少个这种数。

需要注意几点。

  1. dp第二维记录第i+1位数字,整除关系可以传递。中途判断。
    默认之前位都已满足要求,所以只要当前位能整除第i+1位,就可整除前面任意一位。
  2. 满足要求的数中,只有一个数可以含有0,那就是0本身。因为除数不能为0
  3. 对于lead模式下的第二维状态,本来我想用j=10来表示这种“这个数还没开始”的意思,因为这样不会冲突。
    但由于第二点可得知,j=0这个状态其实不会“正常”存在,那就让lead来占这个坑也无妨,也符合本意。
  4. 其实。。我试了一下之后发现,上述第三点是我多虑了,初始调用dfs时无论status是几,都能AC。(因为lead模式下不会用dp值,所以不用担心冲突?)
  5. 总结一下吧。。。
    (1)其实lead模式就是一条生成数字0的链,在这条链上状态应该都不变。
    (2)lead模式可以转为 !lead 模式,反之不成立。
  6. 对这道题而言,在 !lead 模式下对转移的数字有限制。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int T, L, R;
int dp[10][10];                            // j表示上一位
int num[10];

int dfs(int pos, int status, bool lead, bool limit)
{
     
	if (pos == -1) return 1;
	if ((!limit) && (!lead) && (dp[pos][status] != -1)) return dp[pos][status];

	int cnt = 0;
	int up = (limit ? num[pos] : 9);
	for (int i = 0; i <= up; i++)          // 除了前导0和数字0本身,其他都不能有0。因为不能 mod 0
	{
     
		if (!lead)
		{
     
			if (i == 0) continue;          // 绝对不能除以0,所以这句提前
			if ((status % i) != 0) continue;
			if (i > status) break;         // 之后的更大,更不可能
		}

		if (lead && (i == 0)) cnt += dfs(pos - 1, status, true, limit && (i == up));  // 此时status一定等于0
		else cnt += dfs(pos - 1, i, false, limit && (i == up));
	}
	if ((!limit) && (!lead)) dp[pos][status] = cnt;
	return cnt;
}

int solve(int x)                           // 验证 x=0,也要验证当 x!=0 时 solve(x) 是否正确包含了0
{
     
	int pos = 0;
	for (; x;)
	{
     
		num[pos++] = x % 10;
		x /= 10;
	}
	return dfs(pos - 1, 0, true, true);    // lead=true可推得status=0,而且在这道题中,其反向也成立。
}

int main()
{
     
	scanf("%d", &T);
	memset(dp, -1, sizeof dp);
	for (; T--;)
	{
     
		scanf("%d%d", &L, &R);
		printf("%d\n", solve(R) - solve(L - 1));
	}

	return 0;
}

你可能感兴趣的:(动态规划,HDOJ,动态规划,-,数位dp)