POJ 3252 Round Numbers
题意:
让你在给定的区间内找出,2进制数中0的个数不小于1的个数的数有多少个。
思路:
先研究2进制里面0的个数小于等于1的个数,比如12,二进制为:1100。
那么可以分为两个部分:小于1000的符合条件的个数、大于等于1000小于等于1100的符合条件的个数。
一:把小于1000的部分看成 3位二进制数、2位二进制数、1位二进制数的集合。
处理3位二进制数:可以看成2位0: 00中取1个1填充进去和取0个1填充进去(因为首位必须占用一个1),就是C(2,1)和C(2,0)。
处理2位二进制数:可以看成1位0: 0中取0个1填充进去(前面的一个1已经占用了唯一的一个1),就是C(1,0)
那么第一部分就可以提取为:处理n位二进制数,从 n - 1个0中填充小于等于 n / 2 - 1 个1.
第二部分:1100,比这个数小的4位二进制数为:1000,1001,1010,,也就是把第二位置成0,第3、4位随便。
那么推广到一半情况:1101010,从左到右处理(除了第一个1),遇到一个1之后就把这个1置成0,然后从后面的0里面选取位置插1,并且需要记录前面已经有几个1了,防止1的个数超出条件。
另外:如果直接求C(n, k)是会超long long 的,所以我们可以利用递推公式:C(n, k) = C(n, k - 1) * (n - k + 1)/ k.
Code
**#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<bitset>
#include<queue>
#include<stack>
#include<list>
#include<map>
#include<set>
//#define TEST
#define LL long long
#define Mt(f, x) memset(f, x, sizeof(f));
#define rep(i, s, e) for(int i = (s); i <= (e); ++i)
#ifdef TEST
#define See(a) cout << #a << " = " << a << endl;
#define See2(a, b) cout << #a << " = " << a << ' ' << #b << " = " << b << endl;
#define debug(a, s, e) rep(_i, s, e) {cout << a[_i] << ' ';} cout << endl;
#define debug2(a, s, e, ss, ee) rep(i_, s, e) {debug(a[i_], ss, ee)}
#else
#define See(a)
#define See2(a, b)
#define debug(a, s, e)
#define debug2(a, s, e, ss, ee)
#endif // TEST
const int MAX = 2e9;
const int MIN = -2e9;
const double eps = 1e-8;
const double PI = acos(-1.0);
using namespace std;
LL h[35], f[35];
LL jc[50][50];
LL getJC(int n, int k)
{
if(k == 0) return 1;
if(jc[n][k] != -1)
return jc[n][k];
return jc[n][k] = getJC(n, k - 1) * (n - k + 1) / k;
}
void init()
{
Mt(jc, -1);
for(int i = 1; i <= 31; ++i)
{
int top = i / 2;
for(int k = 0; k <= top - 1; ++k)
{
h[i] += getJC(i - 1, k);
}
}
//用f记录下进制位数从1到n的个数
for(int i = 1; i <= 31; ++i)
{
f[i] = f[i - 1] + h[i];
}
}
int toDown(int n)
{
if(!n) return 0;
bitset<33> b(n);
int len = 31;
while(!b[len]) len--;
int ret = f[len];
int o = 1, tem = (len + 1) / 2;
int tlen = len - 1;
bool flag = false;//利用这个看看这个数本身是否是符合条件的
if(tlen >= 0)
flag = true;
while(tlen >= 0)//的二部分的实现代码
{
if(b[tlen])
{
See(tem - o);
for(int i = 0; i <= tem - o; ++i)
{
ret += getJC(tlen, i);
}
o++;
}
if(o > tem)//如果这个数不符合条件(1个个数超出)
{
flag = false;
}
tlen--;
}
return ret + flag;
}
int main()
{
int st, ed;
init();
while(~scanf("%d%d", &st, &ed))
{
LL ans = toDown(ed) - toDown(st - 1);//后面的范围减去前面的范围减一,正好就是所求。
printf("%lld\n", ans);
}
return 0;
}**