【USACO3.2.2】01串 位运算/二分答案

挺有意思的一个题目。


说白了,就是给定一个数字K, 我能否计算出从0~K 这K+1个数字中(全部用二进制表示),  有几个数字是有1个1,有几个数字是2个1,有几个数字有3个1, 有几个数字有4个1……  



这样的问题可以看成是, 有T个空格,我有K个1要插入这T个空格中。  这样的话,这个问题就转化为组合数学问题了。



举个例子:

对于一个二进制数字 11010


我们可以看成是10000 + 1000 + 000 + 10 + 0


分成  0~ 10000 ,  10000~ 11000,   11000~ 11010 这3个部分来计算


实际上也就是0~10000,   0~1000, 0 ~10 这3个部分~!   


因为我要求10000~11000之间,有多少个数字,是有【3】个1的。 实际上是求0~1000之间,有多少个数字,是有【2】个1的。     【这句话要好好理解】



========

那么问题就简单了,把一个二进制数字拆开看,分别用组合数学的知识求出来即可。对于二进制数字拆分成10000+1000+000+10+0的方法,可以用lowbit的方法来解决。 具体程序写的挺清楚的。(deep是计算层数, 因为上面那个让好好理解的那句话, 1的位置越靠后,需要计算的数量越少。)


至于lowbit的原理和应用,可以参考我博客里一篇关于树状数组的介绍。


=====


上面这段话没看懂? 没事, 自己了解一下lowbit这个东西的含义…… 然后其实提示这么多,一般也应该会做了…… 程序速度还算优秀


可以用一个函数cal(a,b) 表示从0~b之间, 有0~a个1组成,所产生的方案总数。    这样题目就变为一个判定性问题,二分答案即可。


Compiling...
Compile: OK

Executing...
   Test 1: TEST OK [0.003 secs, 3380 KB]
   Test 2: TEST OK [0.003 secs, 3380 KB]
   Test 3: TEST OK [0.003 secs, 3380 KB]
   Test 4: TEST OK [0.003 secs, 3380 KB]
   Test 5: TEST OK [0.005 secs, 3380 KB]
   Test 6: TEST OK [0.011 secs, 3380 KB]
   Test 7: TEST OK [0.008 secs, 3380 KB]
   Test 8: TEST OK [0.005 secs, 3380 KB]
   Test 9: TEST OK [0.008 secs, 3380 KB]
   Test 10: TEST OK [0.003 secs, 3380 KB]
   Test 11: TEST OK [0.003 secs, 3380 KB]
   Test 12: TEST OK [0.003 secs, 3380 KB]
   Test 13: TEST OK [0.005 secs, 3380 KB]

All tests OK.


/*
TASK:kimbits
LANG:C++
*/

#include <iostream>
#include <cmath>
#include <cstdio>
using namespace std;
#define lowbit(k) ((k)&(-k))
 
typedef long long LL;
LL N,L,I;
LL c[33][33]={0};
int a[33]={0};

 
int deep;
LL cal(int k, LL tot)
{
	if (!tot)	return 1;
	LL tmp = log(lowbit(tot)) / log(2.0);
	LL ret = cal(k, tot - lowbit(tot));
	++ deep;
	for (int i = 0; i <= k - deep; ++ i)	ret += c[tmp][i];
	return ret;
}
 
 
inline void pg(LL k)
{
	int son[50]={0}, len = N;
	while (k)
	{
		son[len--] = k % 2;
		k /= 2;
	}
	for (int i = 1; i <= N; ++ i)	cout<<son[i];
	cout<<endl;
}
 
int main()
{
	freopen("kimbits.in","r",stdin);
	freopen("kimbits.out","w",stdout);
	for (int i = 0; i != 33; ++ i)	c[i][0] = 1;
	for (int i = 1; i != 33; ++ i)
		for (int j = 1; j <= i; ++ j)	c[i][j] = c[i - 1][j - 1] + c[i - 1][j]; //计算组合数
	cin >> N >> L >> I;
	int p = I, tmp;
	LL low= 0, up = 2300000000LL;
	while (low + 1  < up)   //[ )
	{
		deep = -1;
		LL mid = (low + up) >> 1;
		LL tmp = cal(L, mid);
		if (tmp > I)	up = mid;
		else	low = mid;
	}
	pg(low);
	return 0;
}


你可能感兴趣的:(【USACO3.2.2】01串 位运算/二分答案)