题目描述
有一个程序员,他使用只有两个按键的键盘打字。这两个按键就是0和1,只有达到高端境界的人才能出入此境。记住,只有从l到r的所有数都手打过一遍了才能练就如此功夫。作为真正的高手,你一定能快速地回答区间[l,r]中有多少数字的二进制表示(不含前导零)中0的个数不少于1的个数,因为你每天都进行一遍这个练习。
输入格式
输入一行两个正整数l和r,表示闭区间的左右端点。
输出格式
输出一个整数表示所求答案。
样例
样例输入
2 12
样例输出
6
数据范围与提示
1<=l<r<=2e9
数据范围是2e9那么显然美剧是不可以的(废话,要你说)所以就需要换一种方法,有一种神仙做法叫数位DP (优化暴力枚举)
既然是DP,那么就一定会涉及到状态定义,数位本质就是枚举位数,每一位数字。基本思想就是逐位确定(暴力枚举)。
回到正题,首先我们先确定数位DP的状态。基本思想知道后,定义f[i][0/1][j],表示枚举到第i位,(枚举的是二进制位),其中0的个数减去1的个数为j的方案数。然后就是DP的裸题……
如果不知道什么是数位DP,看这个,
然后做数位DP基础题看这个,这个是有代码的,还有解释……
代码奉上:
#include <bits/stdc++.h>
using namespace std;
int k,b,base,maxn;
int a[100];
int f[100][100][100];
int DP(int n,int a[]){
memset(f,0,sizeof(f));
base=n;
f[1][3][base+1]=1;
maxn=base+n;
for(int i=2;i<=n;++i){
for(int j=0;j<maxn;++j){
if(f[i-1][0][j]){
f[i][0][j-1]+=f[i-1][0][j];
f[i][0][j+1]+=f[i-1][0][j];
}
if(f[i-1][4][j]){
if(a[i]){
f[i][0][j-1]+=f[i-1][5][j];
f[i][6][j+1]+=f[i-1][7][j];
}else f[i][8][j-1]+=f[i-1][9][j];
}
}
}
int ans=0;
for(int i=0;i<=base;++i)ans+=f[n][0][i]+f[n][10][i];
return ans;
}
int Cala(int x){
if(!x)return 0;
int n=0;
while(x)a[++n]=(x&1),x>>=1;
reverse(a+1,a+n+1);
int ans=DP(n,a);
for(int i=1;i<n;++i)a[i]=1;
while(--n)ans+=DP(n,a);
return ans;
}
int main(){
cin>>k>>b;
cout<<Cala(b)-Cala(k-1);
return 0;
}