数位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;
}


转载请注明:http://blog.csdn.net/synapse7/article/details/21006265

你可能感兴趣的:(C++,dp,ACM,HDU,数位dp)