Подсчитать единицы в сегменте (двоичные) - PullRequest
0 голосов
/ 21 ноября 2018

Есть проблема, над которой я сейчас работаю, и она выглядит следующим образом:

есть два числа 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 с n >= 1 равно 1 Пример: 2^1 => num of ones is 1 2^2 => 1 2^15 => 1

выможете проверить это здесь, если хотите: 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)

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

Ответы [ 3 ]

0 голосов
/ 21 ноября 2018
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;

OUTPUT : ones_count равно 12

Вот способ подсчета каждого отдельного бита для каждого значения между двумя значениями.Сдвиг / маска будет быстрее, чем ваши арифметические операторы, скорее всего, но все равно, вероятно, истечет время ожидания.Тебе нужен умный алгоритм, как мне кажется из другого ответа, но вот глупый метод грубой силы:)

0 голосов
/ 21 ноября 2018

Вы можете решить эту проблему, рассчитав количество бит в диапазоне от 1 до n и используя простое вычитание для любого поддиапазона:

#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;
}
0 голосов
/ 21 ноября 2018

Вот предложение по ускорению:

  1. Найдите наименьшее y1 такое, что y1> = x1 и y1 является степенью 2

  2. Найдите наибольшее y2 такое, что y2 <= x2, а y2 - степень 2 </p>

  3. Найдите p1 и p2 такие, что 2 ^ p1 = y1 и 2 ^ p2 = y2

  4. Рассчитать сумму 1: s между y1 и y2

  5. Разобраться с x1 до y1 и y2 до x2 отдельно

  6. Суммируйте результаты 4 и 5

Давайте сосредоточимся на шаге 4. Пусть f (n) будет суммой единиц до (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.

РЕДАКТИРОВАТЬ:

Может быть, я должен был быстро сказать "быстро понять".Вот способ понять это.Количество до 2¹-1 легко увидеть.Только два двоичных числа ниже 2¹ - это 0 и 1. Чтобы получить число единиц до 2², мы берем числа ниже 2¹ и делаем столбец:

0
1

Клонируем его:

0
1
0
1

И поставить 0: s перед первой половиной и 1: s перед второй половиной:

00
01
10
11

Чтобы получить 2³, мы делаем то же самое.Клонируйте его:

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^ (п-1).Клонирование дает 2f (n-1) и добавление 0: s и 1: s дает 2 ^ (n-1).Если 2 ^ (n-1) трудно понять, помните, что 2 ^ (n-1) = (2 ^ n) / 2.На каждом шаге у нас есть 2 ^ n строк, и половина из них получает дополнительные 1.

EDIT2:

Когда я посмотрел на эти столбцы, у меня появилась идея, как выполнить шаг 5.Допустим, вы хотите найти суммы 1: с от 10 до 15. Двоичная таблица для этого будет:

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

Я сделал изображение, чтобы вы могли легче понятьчто a, b и c делает в функции numberOfOnes, если мы вызываем ее с помощью numberOfOnes(12):

enter image description here

Iнаконец, преобразовал его в 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...