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

本文深入探讨了一种数位DP问题,定义了一类特殊数字——其二进制表示中0的数量大于等于1的数量,并讨论了如何在给定区间内计算这类数字的总数。文章通过两种状态设计方法对比,阐述了更优的实现方案,并详细解析了代码实现细节。
6万+

被折叠的 条评论
为什么被折叠?



