0
点赞
收藏
分享

微信扫一扫

数位DP小记 + HDU 2089 不要62


【背景】


如何求出在给定区间[A,B]内,符合条件P(i)的数i的个数?


条件P(i)一般与数的大小无关,而与 数的组成有关,有一下几种P(i):


数i是递增/递减的:1234, 2579,…


双峰的:19280,26193,…


含/不含某一数字的,比如含49:49, 149, 1492,…  (见下方例题)


被某一数m整除的,比如m=13:39,130,650...  (见 Codeforces 410D Roman and Numbers )


【思路】

采用记忆化搜索实现。

搜索:dfs(i,j,k,ismax)


枚举第i位的数,匹配str[j],前一位是k,是否达到上限(ismax=true/false)


达到了上限只能统计cnt=sum(0~num[i]),否则可以统计cnt=sum(0~9)


记忆:return ismax ? cnt : dp[i][j][k] = cnt


为何这么做见下方代码注释


搜索入口:dfs(len,-1,-1,true)



【例题】


http://acm.hdu.edu.cn/showproblem.php?pid=2089



这题的P(i)是不含有4和62的数,那么只需dfs(i,is6,ismax)即可,表示当前搜索到第i位,前一位是否为6(因为要判断后面那个是否为2),是否达到上限。记忆dp[i][ismax],搜索入口为dfs(len,false,true)


完整代码:

/*15ms,232KB*/

#include<cstdio>
#include<cstring>
const int mx = 10;

int bit[mx], dp[mx][2];

///复杂度O(log n)
///若ismax为true则后面循环的时候i只能取0~bit[len]
///is6记录上一位是否为6
int dfs(int len, bool is6, bool ismax)
{
	if (len == 0) return 1; ///能递归到这里说明这串数符合要求,返回1
	if (!ismax && dp[len][is6] >= 0) return dp[len][is6];
	///若ismax为true,则还需要继续向下递归
	///为什么?对于n=5321来说,递归中的2xxx和3xxx可以直接在len=3时返回(因为xxx这颗子树已经被前面的1xxx算出来了)
	///但是在算5xxx时并不能直接返回,因为后面的xxx至多能取到321,还需要进一步往下递归
	int cnt = 0, maxnum = (ismax ? bit[len] : 9);
	for (int i = 0; i <= maxnum; ++i)
	{
		if (i == 4 || is6 && i == 2) continue; ///不能有4,或者前一位为6且该位为2
		cnt += dfs(len - 1, i == 6, ismax && i == maxnum); ///ismax && i == maxnum 用来判断是否达到位值上限
	}
	return ismax ? cnt : dp[len][is6] = cnt; ///根据ismax来决定是否记录dp(比如dp[3][1]记录的是6xx的所有数,即从600到699中的符合条件的数的个数)
}

int f(int n)
{
	int len = 0;
	while (n)
	{
		bit[++len] = n % 10;
		n /= 10;
	}
	return dfs(len, false, true); ///从首位开始递归统计
}

int main()
{
	int a, b;
	memset(dp, -1, sizeof(dp));
	while (scanf("%d%d", &a, &b), a)
		printf("%d\n", f(b) - f(a - 1));
	return 0;
}



举报

相关推荐

0 条评论