1. 程式人生 > 實用技巧 >0和1的熟練

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