LuoguP6218 [USACO06NOV] Round Numbers S
阿新 • • 發佈:2020-07-24
題目描述
如果一個正整數的二進位制表示中,\(0\)的數目不小於\(1\)的數目,那麼它就被稱為「圓數」。
例如,\(9\)的二進位制表示為\(1001\),其中有\(2\)個\(0\)與\(2\)個\(1\)。因此,\(9\)是一個「圓數」。
請你計算,區間\([l,r]\)中有多少個「圓數」。
輸入格式
一行,兩個整數\(l\)和\(r\)。
輸出格式
一行,一個整數,表示區間\([l,r]\)中「圓數」的個數。
樣例
輸入:\(2 12\) 輸出: \(6\)
思路
顯然這道題又是一道數位DP。但是這個題的難點和特殊之處就在於它是在二進位制下處理的。這就需要我們重新揣度此題的狀態。
設\(f[i][j][k]\)表示一個有\(i\)位,且其中包括\(j\)個\(1\),且從右往左數第\(i\)個數是\(k\)的圓數的個數。這個如何轉移呢?
顯然的是,若\(j < i\), \(f[i][j][0]=f[i-1][j][0]+f[i-1][j][1]\),若\(j !=0\), 則\(f[i][j][1]=f[i-1][j-1][0]+f[i-1][j-1][1]\) (這個感性理解一下?)
最後就分成兩種情況處理:
1.將位數小於cnt位的圓數累加到答案中
2.和其他數位DP類似,考慮用添數的方法解決問題。若當前遍歷到該位為\(1\),那麼就存在該位為\(0\)的情況,這時候再列舉\(1\)
最後字首和統計答案即可。
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> typedef long long ll; int l, r; ll f[35][35][2]; inline int read(void){ int f = 1, x = 0;char ch; do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9'); do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9'); return f * x; } inline void _init(void){ f[1][0][0] = 1, f[1][1][1] = 1;//初始化 for (int i = 2; i <= 32;++i){ for (int j = 0; j <= i;++j){ if(j<i) f[i][j][0] = f[i - 1][j][0] + f[i - 1][j][1];//若既有0又有1或只有0 if(j) f[i][j][1] = f[i - 1][j - 1][0] + f[i - 1][j - 1][1];//若有1或全部是1 } } return; } inline ll calc(int x){ int bn[35], cnt = 0; ll res = 0; if(x==0) return 1; while(x){ bn[++cnt] = x & 1; x >>= 1; }//二進位制拆分 for (int i = 1; i < cnt;++i){ for (int j = 0; j <= (i >> 1); ++j) res += f[i][j][1]; }//統計小於cnt位的圓數,(i>>1)這個就是保證了所得的數一定為圓數 int s0 = 0, s1 = 1;//s1一定要賦值為1,因為無論如何我們討論的數都是大於1的(為0的情況在開頭就舍掉了) for (int i = cnt - 1; i >= 1;--i){ if(bn[i]) for (int j = 0; j <= i;++j){ if(s0+i-j>=s1+j) res += f[i][j][0];//已確定的0的個數+列舉的0的個數>=已確定的1的個數+列舉的1的個數 }// s0 + i - j >= s1 + j bn[i] ? ++s1 : ++s0; } return res; } int main(){ l = read(), r = read(); _init(); printf("%lld\n", calc(r + 1) - calc(l)); return 0; }