C语言 计算段中的1(二进制)

mbskvtky  于 2023-02-07  发布在  其他
关注(0)|答案(4)|浏览(215)

有一个问题,我现在正在努力,它是如下:
有两个数x1和x2,且x2〉x1。
例如x1= 5;且x2 = 10;
我必须求出x1和x2的二进制数之和。

5 = 101   => 2 ones
6 = 110   => 2 ones
7 = 111   => 3 ones
8 = 1000  => 1 one
9 = 1001  => 2 ones
10= 1010  => 2 ones
so the sum will be 
sum = 2 + 2 + 3 + 1 + 2 + 2 = 12 ones;

因此,我已经设法使一个代码,甚至没有转移的数字到二进制和浪费执行时间。
我注意到每个2^n中1的个数为n >= 1,例如:x一个三个一个x一个四个一个x一个五个一个
如果需要,您可以在此处进行测试:https://www.rapidtables.com/convert/number/decimal-to-binary.html?x=191
并且在每个2^n and 2^(n+1)之间有连续的数字,正如您将在此示例中看到的:

num              number of ones
2^4 = 16                      1
      17                      2
      18                      2
      19                      3
      20                      2
      21                      3
      22                      3
      23                      4
      24                      2
      25                      3
      26                      3
      27                      4
      28                      3
      29                      4
      30                      4
      31                      5
2^5 = 32                      1

所以我写了一个代码可以找出2^n and 2^(n+1)之间有多少个1

int t;                ////turns
int bin = 1;              //// numbers of ones in the binary format ,,, and 1 for 2^5
int n1 = 32;          //// 2^5  this is just for clarification 
int n2 = 64;          //// 2^6
int *keep = malloc(sizeof(int) * (n2 - n1);             ///this is to keep numbers because 
                                      /// i'll need it later in my consecutive numbers
int i = 0;
int a = 0;
n1 = 33               //// I'll start from 33 cause "bin" of 32 is "1";

while (n1 < n2)       /// try to understand it now by yourself
{
    t = 0;
    while (t <= 3)
    {
        
        if (t == 0 || t == 2) 
            bin = bin + 1;
        
        else if (t == 1) 
            bin = bin;
        
        else if (t == 3)
        {
            bin = keep[i];
            i++;
        }
        keep[a] = bin;
        a++;
        t++;
    }
    n1++;
}

无论如何,正如你所看到的,我接近解决问题,但他们给予了我巨大的数字,我必须找到他们之间的,不幸的是,我已经尝试了很多方法来计算“总和”使用上述代码,我结束了时间执行问题。
例如:1, 1000000000 the numbers of ones is >>> 14846928141
所以你能不能给予我一点提示下一步该怎么做,先谢了。
我这么做是为了代码大战挑战赛https://www.codewars.com/kata/596d34df24a04ee1e3000a25/train/c

ffx8fchx

ffx8fchx1#

可以通过计算1n范围内的位数,并对任何子范围使用简单的减法来解决这个问题:

#include <stdio.h>
#include <stdlib.h>

/* compute the number of bits set in all numbers between 0 and n excluded */
unsigned long long bitpop(unsigned long long n) {
    unsigned long long count = 0, p = 1;
    while (p < n) {
        p += p;
        /* half the numbers in complete slices of p values have the n-th bit set */
        count += n / p * p / 2;
        if (n % p >= p / 2) {
            /* all the numbers above p / 2 in the last partial slice have it */
            count += n % p - p / 2;
        }
    }
    return count;
}    

int main(int argc, char *argv[]) {
    unsigned long long from = 1000, to = 2000;

    if (argc > 1) {
        to = from = strtoull(argv[1], NULL, 0);
        if (argc > 2) {
            to = strtoull(argv[1], NULL, 0);
        }
    }

    printf("bitpop from %llu to %llu: %llu\n", from, to, bitpop(to + 1) - bitpop(from));
    return 0;
}
efzxgjgh

efzxgjgh2#

下面是一个加速建议:
1.找出最小的y1,使y1〉= x1且y1是2的幂
1.找出最大的y2,使y2〈= x2且y2为2的幂
1.求p1和p2,使2^p1 = y1,2^p2 = y2
1.计算y1和y2之间1:s的数量
1.分别处理x1到y1和y2到x2
1.将4和5的结果相加
让我们关注第4步,假设f(n)是1到(2^n)-1的和,我们可以很快地实现f(n)= 2 * f(n-1)+2 ^(n-1)和f(1)= 1,这可以进一步细化,这样你就不必处理递归调用,但我高度怀疑它是否重要,不管怎样,f(n)= n * 2 ^(n-1)
要得到y1和y2之间的结果,只需使用f(p2)-f(p1)
对于步骤5,您可以使用步骤4的修改版本。
编辑:
也许我说"快速实现"太快了。这里有一种理解它的方法。1到2 ¹ -1的数量很容易看到。2 ¹以下的两个二进制数只有0和1。为了得到1到2 ¹的数量,我们取2 ¹以下的数字,并做一列:

0
1

克隆:

0
1
0
1

并将0:s放在前半部分之前,将1:s放在后半部分之前:

00
01
10
11

为了得到2 3,我们做同样的事情。克隆它:

00
01
10
11
00
01
10
11

再加上0和1:

000
001
010
011
100
101
110
111

现在应该很容易理解为什么f(n)= 2 * f(n-1)+2 ^(n-1)了,克隆得到2f(n-1),将0:s和1:s相加得到2 ^(n-1),如果2 ^(n-1)很难理解,请记住2 ^(n-1)=(2^n)/2,在每一步中,我们有2^n行,其中一半得到额外的1。
编辑2:
当我查看这些列时,我想到了如何执行步骤5。假设您要查找从10到15的1:s的数量。用于此操作的二进制表为:

10: 1010
11: 1011
12: 1100
13: 1101
14: 1110
15: 1111

看看12 - 15的区间。二进制的最后两位是0 - 3对应表的副本。这可以利用,但我把它留给你。
编辑3:
这是一个有趣的问题,我写了一些python代码来做这个,我遇到了一些递归调用太多的问题,但这可以很容易地解决,而且把它转换成C应该不会太复杂:

def f(n):
    return n*2**(n-1)

def numberOfOnes(x):
    if(x==0):
        return 0
    p = floor(log(x,2))
    a = f(p)
    b = numberOfOnes(x-2**p)
    c = x - 2**p +1
    return a+b+c

我做了一个图像,这样你就可以更容易地理解,如果我们用numberOfOnes(12)调用函数numberOfOnesabc在函数numberOfOnes中做了什么:

我终于把它转换成了C语言。当然,我使用了一些我在这里找到的关于堆栈溢出的代码。我借用了log2和pow的整数版本的代码,并做了一些小的修改。
这段代码也许可以进一步优化,但这不是必须的。它运行得很快,我无法衡量它的性能。

#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <stdint.h>
#include <inttypes.h>
typedef uint64_t T;

// https://stackoverflow.com/a/11398748/6699433                                                                    
const int tab64[64] = {
    63,  0, 58,  1, 59, 47, 53,  2,
    60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20,
    55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41,
    50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12,
    44, 24, 15,  8, 23,  7,  6,  5};

T log2_64 (T value) {
    value |= value >> 1;
    value |= value >> 2;
    value |= value >> 4;
    value |= value >> 8;
    value |= value >> 16;
    value |= value >> 32;
    return tab64[((T)((value - (value >> 1))*0x07EDD5E59A4E28C2)) >> 58];
}

// https://stackoverflow.com/a/101613/6699433                                                                      
T ipow(T base, T exp) {
    T result = 1;
    for (;;) {
        if (exp & 1) result *= base;
        exp >>= 1;
        if (!exp) break;
        base *= base;
    }
    return result;
}

T f(T n) { return ipow(2,n-1)*n; }

T numberOfOnes(T x) {
    if(x==0) return 0;

    T p = floor(log2(x));
    T a = f(p);
    T e = ipow(2,p);
    T b = numberOfOnes(x-e);
    T c = x - e + 1;
    return a+b+c;
}

void test(T u, T v) {
    assert(numberOfOnes(u) == v);
}

int main() {
    // Sanity checks
    test(0,0);
    test(1,1);
    test(2,2);
    test(3,4);
    test(4,5);
    test(5,7);
    test(6,9);
    // Test case provided in question
    test(1000000000,14846928141);
}
ou6hu8tu

ou6hu8tu3#

int x1 = 5;
int x2 = 10;
int i=0;
int looper = 0;
unsigned long long ones_count = 0;

for(i=x1; i<=x2; i++){
    looper = i;
    while(looper){
        if(looper & 0x01){
            ones_count++;
        }
        looper >>= 1;
    }
}

printf("ones_count is %llu\n", ones_count);
return 0;

输出:一个计数为12

这里有一个方法来计算两个值之间的每个值的每一位。移位/掩码很可能比算术运算符更快,但仍然可能超时。你需要一个聪明的算法,就像另一个答案建议的那样,我认为,但这里是愚蠢的蛮力方法:)

4urapxun

4urapxun4#

这就是我解决问题的方法:

** = exponentiation
/ = whole number division

考虑从1到16的数字:

00001
00010
00011
00100
00101
00110
00111
01000
01001
01010
01011
01100
01101
01110
01111
10000

如果你注意每一列,你会发现一个模式,列索引i (0,1,2 ...)处从右数的位运行一个长度为2**(i+1)的循环,也就是每2**(i+1)行,列i中的模式重复其自身。还要注意,第一个循环从给定列中第一次出现1开始。模式中1的数量是模式长度的一半。

    • 示例:**
i   pattern
0   10
1   1100
2   11110000
3   1111111100000000
   ...

所以,如果要把所有的1加起来,直到n,我们必须记录下每个模式重复了多少次,以及一个模式是否没有完成。

    • 解决方案:**

x为二进制数n的最大指数,sn之前所有1的和。然后,对于i = (0, 1, 2, ... , x),将(n / 2**(i+1)*(2**i)s相加。如果余数大于2**i,则将2**is相加。否则将余数相加,然后从n中减去2**i并重复该过程。

    • 示例:**n = 7 -> x = 2
(7 / 2**1)*(2**0) = 3
7 % 2**1 = 1 !> 2**0
s = 1 + 3     (4)
n = n - 2**0  (6)

(6 / 2**2)*(2**1) = 2
6 % 2**2 = 2 !> 2**1
s = s + 2 + 2  (8)
n = n - 2**1   (4)

(4 / 2**3)*(2**2) = 0
4 % 2**3 = 4 !> 2**2
s = s + 4     (12)
n = n - 2**2  (0)

s = 12

也许不是最好的解释或最漂亮的解决方案,但它工作得很好。
在python中:

def cnt_bin(n):
    bits = n.bit_length()
    s = 0
    for i in range(bits):
        s += (n // 2**(i+1))*2**i
        if n % 2**(i+1) > 2**i:
            s += 2**i
        else:
            s += (n % 2**(i+1))
        n -= 2**i
    return s

然后,对于范围[a, b],只需计算cnt_bin(b) - cnt_bin(a-1)

相关问题