0和1的熟練
阿新 • • 發佈:2020-08-09
題目描述
有一個程式設計師,他使用只有兩個按鍵的鍵盤打字。這兩個按鍵就是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; }