數位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)

【例題】


這題的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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章