用于计算二进制数字范围的1的数量的算法

时间:2010-11-11 19:08:56

标签: algorithm language-agnostic binary

所以我刚刚回到 ACM编程比赛并且表现相当不错,但有一个问题不是一个团队得到的。

问题。

  

以大于N的整数N0开始。令N1为N0的二进制表示中的1的个数。因此,如果N0 = 27N1 = 4。对于所有i > 0,让Ni为Ni-1的二进制表示中的1的个数。该序列将始终收敛于一。对于任何起始编号N0,令K为i> = 0的最小值,其中N1 = 1.例如,如果N0 = 31,则N1 = 5,N2 = 2,N3 = 1,因此K = 3

     

给定一系列连续数字和X值,该范围内有多少数字的K值等于X?

     

输入
  输入中将有几个测试用例。每个测试用例将由一行中的三个整数组成:   LO HI X
  其中LOHI(1 <= LO&lt; = HI&lt; = 10 ^ 18)是整数范围的下限和上限,以及X(0 <= X&lt; = 10)是K的目标值。输入将以三个0的行结束。

     

输出
  对于每个测试用例,输出一个整数,表示从LOHI(包括)范围内的整数数,它们在输入中的K值等于X.在没有空格的情况下在每条线上打印每个Integer。不要在答案之间打印任何空白行。

示例输入

31 31 3
31 31 1
27 31 1
27 31 2
1023 1025 1
1023 1025 2
0 0 0

样本输出

1
0
0
3
1
1

如果你们想要我可以包括我们的答案或我们的问题,因为找到一个小范围很容易,但我会先给你一个提示,你的程序需要在而不是几分钟内运行。我们有一个成功的解决方案,但不是一个有效的算法来使用类似于

的范围
48238 10^18 9

无论如何好运,如果社区喜欢这些,我们还有一些我们无法解决的问题,这对你们来说可能是一些好的脑筋急转弯。竞赛允许您使用Python,C ++或Java--这三个答案都可以接受。


所以作为一个暗示我的教练说要考虑二进制数是如何计算而不是检查每一点。我认为这让我们更加接近。

6 个答案:

答案 0 :(得分:18)

我认为关键是首先要了解K值的模式以及它的增长速度。基本上,你有:

K(1) = 0
K(X) = K(bitcount(X))+1 for X > 1

因此,我们看到给定K的最小X值

K(1) = 0
K(2) = 1
K(3) = 2
K(7) = 3
K(127) = 4
K(170141183460469231731687303715884105727) = 5

因此,对于像48238 10^18 9这样的例子,答案是平凡的0. K = 0仅针对1,而K = 1仅针对2的幂,所以在感兴趣的范围内,我们几乎只会看到K值为2,3或4,并且从未看到K> = 5

修改

好的,所以我们正在寻找一种算法,在值LO..HI的范围内计算K = 2,3,4的值的数量而不迭代整个范围。因此,第一步是找到范围内的值的数量,其中bitcount(x)== i,i = 1..59(因为我们只关心高达10 ^ 18和10 ^ 18 <2 ^ 60的值)。因此将范围分解为2个大小的幂的子范围,并且仅在它们的低n位中不同 - 形式x *(2 ^ n)的范围。(x + 1)*(2 ^ N)-1。我们可以轻松地将仲裁范围分解为这样的子范围。对于每个这样的子范围,将选择具有i + bitcount(x)设置位的(n,i)值。 所以我们只是将所有子范围加在一起得到1..59的计数向量,然后我们迭代,将具有相同K值的元素加在一起得到我们的答案。

编辑(再次修复为C89兼容并适用于lo = 1 / k = 0)

这是一个C程序,可以执行我之前描述的操作:

#include <stdio.h>
#include <string.h>
#include <assert.h>

int bitcount(long long x) {
    int rv = 0;
    while(x) { rv++; x &= x-1; }
    return rv; }

long long choose(long long m, long long n) {
    long long rv = 1;
    int i;
    for (i = 0; i < n; i++) {
        rv *= m-i;
        rv /= i+1; }
    return rv; }

void bitcounts_p2range(long long *counts, long long base, int l2range) {
    int i;
    assert((base & ((1LL << l2range) - 1)) == 0);
    counts += bitcount(base);
    for (i = 0; i <= l2range; i++)
        counts[i] += choose(l2range, i); }

void bitcounts_range(long long *counts, long long lo, long long hi) {
    int l2range = 0;
    while (lo + (1LL << l2range) - 1 <= hi) {
        if (lo & (1LL << l2range)) {
            bitcounts_p2range(counts, lo, l2range);
            lo += 1LL << l2range; }
        l2range++; }
    while (l2range >= 0) {
        if (lo + (1LL << l2range) - 1 <= hi) {
            bitcounts_p2range(counts, lo, l2range);
            lo += 1LL << l2range; }
        l2range--; }
    assert(lo == hi+1); }

int K(int x) {
    int rv = 0;
    while(x > 1) {
        x = bitcount(x);
        rv++; }
    return rv; }

int main() {
    long long counts[64];
    long long lo, hi, total;
    int i, k;
    while (scanf("%lld%lld%d", &lo, &hi, &k) == 3) {
        if (lo < 1 || lo > hi || k < 0) break;
        if (lo == 0 || hi == 0 || k == 0) break;
        total = 0;
        if (lo == 1) {
            lo++;
            if (k == 0) total++; }
        memset(counts, 0, sizeof(counts));
        bitcounts_range(counts, lo, hi);
        for (i = 1; i < 64; i++)
            if (K(i)+1 == k)
                total += counts[i];
        printf("%lld\n", total); }
    return 0; }

对于最多2 ^ 63-1(LONGLONG_MAX)的值运行正常。 对于48238 1000000000000000000 3,它会513162479025364957,这看起来似乎是合理的

修改

给出

的输入
48238 1000000000000000000 1
48238 1000000000000000000 2
48238 1000000000000000000 3
48238 1000000000000000000 4

给出

的输出
44
87878254941659920
513162479025364957
398959266032926842

那些加起来是999999999999951763这是正确的。 k = 1的值是正确的(在该范围内有44个2的幂,2 ^ 16到2 ^ 59)。因此,虽然我不确定其他3个值是否正确,但它们肯定是合理的。

答案 1 :(得分:6)

这个答案背后的想法可以帮助您开发非常快速的解决方案。范围为0..2 ^ N,在最坏情况下,潜在算法的复杂度为O(N)(假设长算术的复杂度为O(1))如果编程正确,则应该很容易处理N = 1000000毫秒。

想象一下,我们有以下价值观:

LO   =          0; (0000000000000000000000000000000)
HI   = 2147483647; (1111111111111111111111111111111)

范围LO..HI中可能的最低N1为0 LO..HI范围内的最高可能N1为31

因此N2..NN部分的计算仅针对32个值之一(即0..31)进行。 即使没有计算机,也可以简单地完成。 现在让我们计算一系列值LO..HI

的N1 = X的数量

当我们有X = 0时,我们有计数(N1 = X)= 1这是以下值:

1   0000000000000000000000000000000

当我们有X = 1时,我们有计数(N1 = X)= 31这些是以下值:

01  1000000000000000000000000000000
02  0100000000000000000000000000000
03  0010000000000000000000000000000
    ...
30  0000000000000000000000000000010
31  0000000000000000000000000000001

当我们有X = 2时,我们有以下模式:

1100000000000000000000000000000

可以用29 - '0'和2 - '1'形成多少个唯一字符串?

想象一下,最右边的'1'(#1)从左到右循环,我们得到以下图片:

01  1100000000000000000000000000000
02  1010000000000000000000000000000
03  1001000000000000000000000000000
    ...
30  1000000000000000000000000000001 

现在我们从左到右移动'1'(#1)时有30个独特的字符串,现在不可能 通过向任意方向移动“1”(#1)来创建唯一的字符串。这意味着我们应该向右移动'1'(#2), 让我们也尽可能地保持'1'(#1)的位置尽可能保持唯一性,我们得到:

01  0110000000000000000000000000000

现在我们再次循环'1'(#1)

02  0101000000000000000000000000000
03  0100100000000000000000000000000
    ...
29  0100000000000000000000000000001

现在我们有29个独特的字符串,继续整个操作28次我们得到以下表达式

count(N1=2) = 30 + 29 + 28 + ... + 1 = 465

当我们有X = 3时,图片仍然相似,但我们正在移动'1'(#1),'1'(#2),'1'(#3)

移动'1'(#1)会创建29个唯一的字符串,当我们开始移动'1'(#2)时,我们得到

29 + 28 + ... + 1 = 435个唯一字符串,之后我们将处理'1'(#3),所以我们有

29 + 28 + ... + 1 = 435
     28 + ... + 1 = 406
          ...
              + 1 = 1

435 + 406 + 378 + 351 + 325 + 300 + 276 +
253 + 231 + 210 + 190 + 171 + 153 + 136 +
120 + 105 + 091 + 078 + 066 + 055 + 045 +
036 + 028 + 021 + 015 + 010 + 006 + 003 + 001 = 4495

让我们尝试解决一般情况,即当我们有N个零和M个时。 长度字符串(N + M)的总排列量等于(N + M)!

此字符串中'0'重复的数量等于N! 此字符串中“1”重复的数量等于M!

因此接收由N个零和M个形成的唯一字符串的总量是

              (N + M)!          32!       263130836933693530167218012160000000
F(N, M) =  ============= => ========== = ====================================== = 4495
            (N!) * (M!)      3! * 29!      6 * 304888344611713860501504000000

修改

F(N, M) = Binomial(N + M, M)

现在让我们考虑一个现实生活中的例子:

LO   =   43797207; (0000010100111000100101011010111)
HI   = 1562866180; (1011101001001110111001000000100)

那么我们如何将这个独特的排列公式应用于这个例子呢?既然我们不知道如何 很多'1'位于LO之下,有多少'1'位于HI之上。

因此,让我们将这些排列计算在低于LO和高于HI的位置。

让我们记住我们如何循环'1'(#1),'1'(#2),......

1111100000000000000000000000000 => 2080374784
1111010000000000000000000000000 => 2046820352
1111001000000000000000000000000 => 2030043136
1111000000000000000000000000001 => 2013265921

1110110000000000000000000000000 => 1979711488
1110101000000000000000000000000 => 1962934272
1110100100000000000000000000000 => 1954545664
1110100010000000000000000000001 => 1950351361

如您所见,此循环过程会平滑地减小小数值。所以我们需要数量 循环直到我们达到HI值。但我们不应该将这些值计算为1,因为 最糟糕的情况可以产生高达32!/(16!* 16!)= 601080390次循环,我们将骑自行车很长:) 所以我们需要一次循环“1”。

有了我们的例子,我们想要计算转换的周期数

1111100000000000000000000000000 => 1011101000000000000000000000000
                                   1011101001001110111001000000100

因此有多少周期导致转换

1111100000000000000000000000000 => 1011101000000000000000000000000

让我们看看,转型:

1111100000000000000000000000000 => 1110110000000000000000000000000

等于以下一组循环:

01  1111100000000000000000000000000
02  1111010000000000000000000000000
...
27  1111000000000000000000000000001
28  1110110000000000000000000000000

所以我们需要28个周期进行转换

1111100000000000000000000000000 => 1110110000000000000000000000000

我们需要转换多少个周期

1111100000000000000000000000000 => 1101110000000000000000000000000

执行我们需要的以下动作:

1110110000000000000000000000000 28 cycles
1110011000000000000000000000000 27 cycles
1110001100000000000000000000000 26 cycles
...
1110000000000000000000000000011  1 cycle

和1个接收周期:

1101110000000000000000000000000  1 cycle

因此接收28 + 27 + ... + 1 + 1 = 406 + 1

但我们之前已经看到过这个值,它是唯一排列量的结果 计算为2'1'和27'0'。这意味着移动时的循环量

11100000000000000000000000000 => 01110000000000000000000000000

等于移动

_1100000000000000000000000000 => _0000000000000000000000000011 

加上一个额外的周期

所以这意味着如果我们有M个零和N个,并且想要将U'1'的块移到右边,我们将需要 执行以下循环次数:

      (U - 1 + M)!
1 + =============== = f(U, M)
     M! * (U - 1)!

修改

f(U, M) = 1 + Binomial(U - 1 + M, M)

现在让我们回到我们现实生活中的例子:

    LO   =   43797207; (0000010100111000100101011010111)
    HI   = 1562866180; (1011101001001110111001000000100)

所以我们要做的是计算执行以下操作所需的周期数 转换(假设N1 = 6)

1111110000000000000000000000000 => 1011101001000000000000000000000
                                   1011101001001110111001000000100

这等于:

1011101001000000000000000000000    1011101001000000000000000000000
-------------------------------    -------------------------------
_111110000000000000000000000000 => _011111000000000000000000000000 f(5, 25) = 118756
_____11000000000000000000000000 => _____01100000000000000000000000 f(2, 24) = 301
_______100000000000000000000000 => _______010000000000000000000000 f(1, 23) = 24
________10000000000000000000000 => ________01000000000000000000000 f(1, 22) = 23

因此导致119104'丢失'循环,其位于HI

之上

关于LO,我们骑自行车的方向实际上没有区别 所以对于计算LO我们可以做反向循环:

0000010100111000100101011010111    0000010100111000100101011010111
-------------------------------    -------------------------------
0000000000000000000000000111___ => 0000000000000000000000001110___ f(3, 25) = 2926
00000000000000000000000011_____ => 00000000000000000000000110_____ f(2, 24) = 301

因此导致3227'丢失'周期位于LO之下,这意味着

overall amount of lost cycles = 119104 + 3227 = 122331

overall amount of all possible cycles = F(6, 25) = 736281

N1 in range 43797207..1562866180 is equal to 736281 - 122331 = 613950

我不会提供解决方案的剩余部分。掌握其余部分并不难。祝你好运!

答案 2 :(得分:2)

我认为这是离散数学中的一个问题,
假设LOW为0,
否则我们可以插入一个函数来汇总低于LOW的数字,
从显示的数字我知道最长的数字最多将包含60个二进制数字

alg(HIGH,k)

l=len(HIGH)
sum=0;

for(i=0;i<l;i++)
{
 count=(l choose i); 
 nwia=numbers_with_i_above(i,HIGH); 
 if canreach(i,k) sum+=(count-nwia);
}


所有数字都出现了 非列出两次 numbers_with_i_above是微不足道的 数字高达60的canreach很容易 len是二进制代表的长度

答案 3 :(得分:1)

Zobgib,

这个问题的关键在于不了解K模式的增长速度有多快,但是它本身如何增长。这样做的第一步是理解(正如你的教练所说)二进制数如何计算,因为这决定了K的确定方式。二进制数遵循在计算正位数时不同的模式。它是一个渐进的重复模式。我将以一种不寻常的方式展示......

Assume i is an integer value. Assume b is the number of positive bits in i
i = 1; 
b = 1; 

i = 2; 3;
b = 1; 2;

i = 4; 5; 6; 7;
b = 1; 2; 2; 3;

i = 8; 9; 10; 11; 12; 13; 14; 15;
b = 1; 2;  2;  3;  2;  3;  3;  4;

i = 16; 17; 18; 19; 20; 21; 22; 23; 24; 25; 26; 27; 28; 29; 30; 31;
b =  1;  2;  2;  3;  2;  3;  3;  4;  2;  3;  3;  4;  3;  4;  4;  5; 

I assure you, this pattern holds to infinity, but if needed you
should be able to find or construct a proof easily.

如果查看上面的数据,您会发现与2 ^ n相关的明显模式。每次整数指数为2时,模式将通过包含前一个模式的每个项重置,然后前一个模式的每个项增加1.因此,要获得K,只需将新数应用于上面的图案。关键是找到一个表达式(这是有效的)来接收你的位数。

为了演示,再一次,你可以进一步推断一个新的模式,因为它是静态的并且遵循相同的进展。下面是用K值修改的原始数据(基于递归)。

Assume i is an integer value. Assume b is the number of positive bits in i
i = 1; 
b = 1; 
K = 1;

i = 2; 3;
b = 1; 2;
K = 1; 2;

i = 4; 5; 6; 7;
b = 1; 2; 2; 3;
K = 1; 2; 2; 3;

i = 8; 9; 10; 11; 12; 13; 14; 15;
b = 1; 2;  2;  3;  2;  3;  3;  4;
K = 1; 2;  2;  3;  2;  3;  3;  2;

i = 16; 17; 18; 19; 20; 21; 22; 23; 24; 25; 26; 27; 28; 29; 30; 31;
b =  1;  2;  2;  3;  2;  3;  3;  4;  2;  3;  3;  4;  3;  4;  4;  5; 
K =  1;  2;  2;  3;  2;  3;  3;  2;  2;  3;  3;  2;  3;  2;  2;  3;

如果你注意到,K遵循类似的模式,具有特殊条件......每次b是2的幂,它实际上将K值降低2. Soooo,如果你遵循二进制进展,你应该能够轻松映射您的K值。由于这种模式依赖于2的幂,并且模式依赖于找到最近的2的幂并从那里开始,所以我提出以下解决方案。取你的LOW值,找到最近的2(p)幂,2^p < LOW。这可以通过“计数位”来获得最小数量。同样,一旦你知道它是哪个指数,你就不必计算任何其他数字的位数。你只需通过模式递增,你将获得你的b,因此K(遵循相同的模式)。

注意:如果您特别注意,可以使用之前的bK来确定下一个。如果当前i为奇数,请将1添加到上一个b。如果当前i可被4整除,那么您将b递减1或2,具体取决于它是在模式的前1/2还是后半部分。当然,如果i是2的幂,则从1开始。

模糊逻辑

伪代码示例(未优化)

{   var LOW, HIGH
    var power = 0

//Get Nearest Power Of 2 for (var i = 0 to 60) { // Compare using bitwise AND if (LOW bitAND (2 ^ i) = (2 ^ i)) { if ((2 ^ i) <= LOW) { set power to i } else { // Found the Power: end the for loop set i to 61 } } }

// Automatically 1 at a Power of 2 set numOfBits to 1 array numbersWithPositiveBits with 64 integers = 0 // Must create the pattern from Power of 2 set foundLOW to false for (var j = (2^power) to HIGH) { set lenOfPatten to (power + 1) // Don't record until we have found the LOW value if ((foundLOW is false) bitAND (j is equal to LOW)) { set foundLOW to true } // If j is odd, increment numOfBits if ((1 bitAND j) is equal to 1) { increment numOfBits } else if (j modulus 4 == 0) { decrement numOfBits accordingly //Figure this one out yourself, please } else if ((j - (2^power)) == (power + 1)) { // We are at the next power increment power // Start pattern over set numOfBits to 1 } // Record if appropriate if (foundLOW is equal to true) { increment element numOfBits in array numbersWithPositiveBits } }

// From here, derive your K values.

答案 4 :(得分:0)

您可以按如下方式有效解决此问题:

ret = 0;
for (i = 1; i <= 64; i++) {
  if (computeK(i) != desiredK) continue;
  ret += numBelow(HIGH, i) - numBelow(LO - 1, i);
}
return ret;

函数numBelow(high, numSet)计算小于或等于high且大于零且已设置numSet位的整数数。要有效地实施numBelow(high, numSet),您可以使用以下内容:

numBelow(high, numSet) {
  t = floor(lg(high));
  ret = 0;
  if (numBitsSet(high) == numSet) ret++;
  while (numSet > 0 && t > 0) {
    ret += nchoosek(t - 1, numSet);
    numSet--;
    while (--t > 0 && (((1 << t) & high) == 0));
  }
  return ret;
}

答案 5 :(得分:0)

这是一个完整的 c++17 示例

#include <bits/stdc++.h>

using namespace std;

#define BASE_MAX 61

typedef unsigned long long ll;

ll combination[BASE_MAX][BASE_MAX];

vector<vector<ll>> NK(4);

int count_bit(ll n) {
    int ret = 0;
    while (n) {
        if (n & 1) {
            ret++;
        }
        n >>= 1;
    }

    return ret;
}

int get_leftmost_bit_index(ll n) {
    int ret = 0;
    while (n > 1) {
        ret++;
        n >>= 1;
    }
    return ret;
}

void pre_calculate() {
    for (int i = 0; i < BASE_MAX; i++)
        combination[i][0] = 1;
    for (int i = 1; i < BASE_MAX; i++) {
        for (int j = 1; j < BASE_MAX; j++) {
            combination[i][j] = combination[i - 1][j] + combination[i - 1][j - 1];
        }
    }

    NK[0].push_back(1);
    for (int i = 2; i < BASE_MAX; i++) {
        int bitCount = count_bit(i);
        if (find(NK[0].begin(), NK[0].end(), bitCount) != NK[0].end()) {
            NK[1].push_back(i);
        }
    }
    for (int i = 1; i < BASE_MAX; i++) {
        int bitCount = count_bit(i);
        if (find(NK[1].begin(), NK[1].end(), bitCount) != NK[1].end()) {
            NK[2].push_back(i);
        }
    }
    for (int i = 1; i < BASE_MAX; i++) {
        int bitCount = count_bit(i);
        if (find(NK[2].begin(), NK[2].end(), bitCount) != NK[2].end()) {
            NK[3].push_back(i);
        }
    }
}


ll how_many_numbers_have_n_bit_in_range(ll lo, ll hi, int bit_count) {
    if (bit_count == 0) {
        if (lo == 0) return 1;
        else return 0;
    }

    if (lo == hi) {
        return count_bit(lo) == bit_count;
    }

    int lo_leftmost = get_leftmost_bit_index(lo); // 100 -> 2
    int hi_leftmost = get_leftmost_bit_index(hi); // 1101 -> 3

    if (lo_leftmost == hi_leftmost) {
        return how_many_numbers_have_n_bit_in_range(lo & ~(1LL << lo_leftmost), hi & ~(1LL << hi_leftmost),
                                                    bit_count - 1);
    }

    if (lo != 0) {
        return how_many_numbers_have_n_bit_in_range(0, hi, bit_count) -
               how_many_numbers_have_n_bit_in_range(0, lo - 1, bit_count);
    }

    ll ret = combination[hi_leftmost][bit_count];

    ret += how_many_numbers_have_n_bit_in_range(1LL << hi_leftmost, hi, bit_count);

    return ret;
}

int main(void) {
    pre_calculate();

    while (true) {
        ll LO, HI;
        int X;
        scanf("%lld%lld%d", &LO, &HI, &X);
        if (LO == 0 && HI == 0 && X == 0)
            break;

        switch (X) {
            case 0:
                cout << (LO == 1) << endl;
                break;
            case 1: {
                int ret = 0;
                ll power2 = 1;
                for (int i = 0; i < BASE_MAX; i++) {
                    power2 *= 2;

                    if (power2 > HI)
                        break;
                    if (power2 >= LO)
                        ret++;
                }
                cout << ret << endl;
                break;
            }
            case 2:
            case 3:
            case 4: {
                vector<ll> &addedBitsSizes = NK[X - 1];

                ll ret = 0;

                for (auto bit_count_to_added: addedBitsSizes) {
                    ll result = how_many_numbers_have_n_bit_in_range(LO, HI, bit_count_to_added);
                    ret += result;
                }

                cout << ret << endl;
                break;
            }
            default:
                cout << 0 << endl;
                break;
        }
    }

    return 0;
}