POJ 3252 Round Numbers

传送门

数位dp。
定义一种数,其二进制表示中0的个数大于等于1的个数。给你[Start,Finish],问其中多少个这种数。

说几点。

  1. 首先可以想到,这个题没办法中途continue了,只能到最后(pos==-1)才能判断。
  2. 状态可以有两种设计方法,一种是两维,分别表示前面数位中01的个数;
    另一种是一维,表示前面数位中0多于1的个数(必须确定谁比谁多,请不要混淆为绝对值)。
    可以看出,后者更优。
  3. 上述状态值有可能是负数。所以设置MID=40作为虚拟的0,保证dfs过程中怎么也不会变负就可以了,最后判断是否>=MID(中途完全可以不满足)。
  4. 这道题要在solve()中逐个得到二进制数位,然后在二进制上进行dfs。
  5. 重点
    这道题要考虑前导0了,其实lead模式和limit模式差不多,lead模式一路顺着生成了数字0,limit模式一路顺着生成了x。只不过,在lead模式下,状态值始终是初始值(MID)。
    (在从高位到低位的dfs过程中,对这两种模式都有:只要当前是false了,往后就都是false了)
  6. lead模式下也不能应用dp值,考虑1010 010000 01
  7. 其实lead模式和limit模式不会同时满足(除了最开始调用时),因为由solve()产生的数x的最高位不可能是0
  8. 对于这种较为复杂的题,要检查solve(0)的正确性以及solve(x)是否正确判断了0
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

int Start, Finish;
int dp[32][80];
int num[32];

const int MID = 40;

int dfs(int pos, int status, bool lead, bool limit)        // lead
{
     
	if (pos == -1) return (status >= MID ? 1 : 0);         // 只能最后判断(中途完全可以不满足这一点)
	if ((!limit) && (!lead) && (dp[pos][status] != -1)) return dp[pos][status];

	if ((!lead) && ((status + pos + 1) < MID)) return 0;   // 剪枝...没什么用

	int cnt = 0;
	int up = (limit ? num[pos] : 1);
	for (int i = 0; i <= up; i++)
	{
     
		if (lead && (i == 0)) cnt += dfs(pos - 1, status, true, limit && (i == up));   // 这里status一定是40
		else cnt += dfs(pos - 1, i == 0 ? (status + 1) : (status - 1), false, limit && (i == up));
	}
	if ((!limit) && (!lead)) dp[pos][status] = cnt;
	return cnt;
}

int solve(int x)               // 要测试0是否可行
{
     
	int pos = 0;
	for (; x;)
	{
     
		num[pos++] = x % 2;    // 2进制
		x /= 2;
	}
	return dfs(pos - 1, MID, true, true);
}

int main()
{
     
	memset(dp, -1, sizeof dp);
	for (; ~scanf("%d%d", &Start, &Finish);)
		printf("%d\n", solve(Finish) - solve(Start - 1));

	return 0;
}

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