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