0和1的熟练

题目描述
有一个程序员,他使用只有两个按键的键盘打字。这两个按键就是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;
}
原文地址:https://www.cnblogs.com/gdxzq/p/13463570.html