一个数的二进制中0的数量要不能少于1的数量。
// 非常非常非常推荐我转载的一篇大佬写的数位DP总结,Orz、
思路1:
// 自己想哒 16ms
dp[pos][k]记录的是枚举到pos位,最多还能用k个1的数量。可用1的数量是随着枚举数不同而变化的。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m;
int a[50];
int dp[50][100];
int dfs(int pos,int len,int k,bool limit,bool lead){ //k记录的是已经用了1的个数
if(len/2-k<0)return 0; //len/2-1记录的是还可用1的个数
if(pos==-1)return 1;
if(!limit&&!lead&&dp[pos][len/2-k]!=-1)return dp[pos][len/2-k];
int ans=0;
int up=limit?a[pos]:1;
for(int i=0;i<=up;i++){//len记录的是第一个二进制数从1开始的长度(前导0,长度减1)
if(lead&&i==0)ans+=dfs(pos-1,len-1,k+i,limit&&i==a[pos],1);
else ans+=dfs(pos-1,len,k+i,limit&&i==a[pos],0);
}
if(!limit&&!lead)dp[pos][len/2-k]=ans;
return ans;
}
int solve(int x){
int pos=0;
while(x){
a[pos++]=x&1;
x>>=1;
}
return dfs(pos-1,pos,0,1,1);
}
int main()
{
scanf("%d%d",&n,&m);
memset(dp,-1,sizeof(dp));
printf("%d\n",solve(m)-solve(n-1));
return 0;
}
思路2:
// 大佬思路 0ms(Orz、)
这题的约束就是一个数的二进制中0的数量要不能少于1的数量,通过上一题,这题状态就很简单了,dp[pos][num],到当前数位pos,0的数量减去1的数量不少于num的方案数,一个简单的问题,中间某个pos位上num可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的num>=0才能判合法,中途某个pos就不一定了),这里比较好处理,Hash嘛,最小就-32吧(好像),直接加上32,把32当0用。这题主要是要想讲一下lead的用法,显然我要统计0的数量,前导零是有影响的。至于!lead&&!limit才能dp,都是类似的,自己慢慢体会吧。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,m;
int a[50];
int dp[50][100];
int dfs(int pos,int num,bool limit,bool lead){
if(pos==-1)return num>=50;
if(!limit&&!lead&&dp[pos][num]!=-1)return dp[pos][num];
int ans=0;
int up=limit?a[pos]:1;
for(int i=0;i<=up;i++){
if(lead&&i==0)ans+=dfs(pos-1,num,limit&&i==a[pos],1);
else ans+=dfs(pos-1,num+(i==0?1:-1),limit&&i==a[pos],0);
}
if(!limit&&!lead)dp[pos][num]=ans;
return ans;
}
int solve(int x){
int pos=0;
while(x){
a[pos++]=x&1;
x>>=1;
}
return dfs(pos-1,50,1,1);
}
int main()
{
scanf("%d%d",&n,&m);
memset(dp,-1,sizeof(dp));
printf("%d\n",solve(m)-solve(n-1));
return 0;
}