2012-01-26 2 views
26

Я наткнулся на этот вопрос. Число называется счастливым, если сумма его цифр, а также сумма квадратов его цифр - это простое число. Сколько чисел между A и B повезло? 1 < = A < = B < = 10 . Я попробовал это.

Алгоритм поиска Lucky Numbers

  • Сначала я генерироваться все возможные простые числа между 1 и числом, которые могут быть приведены путем суммирования квадратов (81 * 18 = 1458).

  • Я прочитал в A и B, чтобы узнать максимальное число, которое может быть сгенерировано путем суммирования цифр. Если B является 2-значным номером (максимальное число равно 18, генерируемому на 99).

  • Для каждого простого числа от 1 до максимального числа. Я применил целочисленный алгоритм разбиения.

  • Для каждого возможного раздела я проверил, является ли их сумма квадратов их цифр простой. Если это так, то возможны перестановки этого раздела, и если они лежат в радиусе, это удачные числа.

Это реализация:

#include<stdio.h> 
#include<malloc.h> 
#include<math.h> 
#include <stdlib.h> 
#include<string.h> 
long long luckynumbers; 
int primelist[1500]; 

int checklucky(long long possible,long long a,long long b){ 
    int prime =0; 
    while(possible>0){ 
      prime+=pow((possible%10),(float)2); 
      possible/=10; 
    } 
     if(primelist[prime]) return 1; 
     else return 0; 
} 
long long getmax(int numdigits){ 
     if(numdigits == 0) return 1; 
     long long maxnum =10; 
      while(numdigits>1){ 
         maxnum = maxnum *10; 
         numdigits-=1; 
      } 
     return maxnum; 

} 
void permuteandcheck(char *topermute,int d,long long a,long long b,int digits){ 
    if(d == strlen(topermute)){ 
      long long possible=atoll(topermute); 
      if(possible >= getmax(strlen(topermute)-1)){ // to skip the case of getting already read numbers like 21 and 021(permuted-210 

       if(possible >= a && possible <= b){ 

        luckynumbers++; 
       } 
      } 
    } 
    else{ 
     char lastswap ='\0'; 
     int i; 
     char temp; 
     for(i=d;i<strlen(topermute);i++){ 
      if(lastswap == topermute[i]) 
       continue; 
      else 
       lastswap = topermute[i]; 
      temp = topermute[d]; 
      topermute[d] = topermute[i]; 
      topermute[i] = temp; 

      permuteandcheck(topermute,d+1,a,b,digits); 

      temp = topermute[d]; 
      topermute[d] = topermute[i]; 
      topermute[i] = temp; 
     } 

    } 

} 


void findlucky(long long possible,long long a,long long b,int digits){ 
    int i =0; 
    if(checklucky(possible,a,b)){ 
     char topermute[18]; 
     sprintf(topermute,"%lld",possible); 
     permuteandcheck(topermute,0,a,b,digits); 
    } 
} 


void partitiongenerator(int k,int n,int numdigits,long long possible,long long a,long long b,int digits){ 
    if(k > n || numdigits > digits-1 || k > 9) return; 
    if(k == n){ 

     possible+=(k*getmax(numdigits)); 

     findlucky(possible,a,b,digits); 
     return; 
    } 
    partitiongenerator(k,n-k,numdigits+1,(possible + k*getmax(numdigits)),a,b,digits); 
    partitiongenerator(k+1,n,numdigits,possible,a,b,digits); 

} 


void calcluckynumbers(long long a,long long b){ 
    int i; 
    int numdigits = 0; 
    long long temp = b; 
    while(temp > 0){ 
     numdigits++; 
     temp/=10; 
    } 

    long long maxnum =getmax(numdigits)-1; 
    int maxprime=0,minprime =0; 
    temp = maxnum; 
    while(temp>0){ 
     maxprime+=(temp%10); 
     temp/=10; 
    } 
    int start = 2; 
    for(;start <= maxprime ;start++){ 
      if(primelist[start]) { 
       partitiongenerator(0,start,0,0,a,b,numdigits); 
      } 
    } 

} 
void generateprime(){ 
    int i = 0; 
    for(i=0;i<1500;i++) 
     primelist[i] = 1; 
    primelist[0] = 0; 
    primelist[1] = 0; 
    int candidate = 2; 
    int topCandidate = 1499; 
    int thisFactor = 2; 
    while(thisFactor * thisFactor <= topCandidate){ 
     int mark = thisFactor + thisFactor; 
     while(mark <= topCandidate){ 
      *(primelist + mark) = 0; 
      mark += thisFactor; 
     } 
     thisFactor++; 
     while(thisFactor <= topCandidate && *(primelist+thisFactor) == 0) thisFactor++; 
    } 

} 
int main(){ 
     char input[100]; 
     int cases=0,casedone=0; 
    long long a,b; 
    generateprime(); 
     fscanf(stdin,"%d",&cases); 
     while(casedone < cases){ 
     luckynumbers = 0; 
       fscanf(stdin,"%lld %lld",&a,&b); 
     int i =0; 
       calcluckynumbers(a,b); 
       casedone++; 
     } 

} 

алгоритм слишком медленно. Я думаю, что ответ можно найти на основе свойства чисел. Поделитесь своими мыслями. Спасибо.

+1

Ошибка: 'primelist' имеет размер 1400, но вы относитесь к нему так, как если бы он имел размерность 1500 –

+0

Я думаю, что этот вопрос нужно переместить на http://codereview.stackexchange.com/ –

+0

@Paul R, я не думаю это большое дело – batbaatar

ответ

15

Отличное решение OleGG, но ваш код не оптимизирован. Я сделал следующие изменения в свой код,

  1. Он не требует, чтобы пройти через 9 * 9 * я для к в count_lucky функции, потому что для 10000 случаев она будет работать, что много раз, вместо того, чтобы я свел значение через начало и конец.

  2. Я использовал массив ans для хранения промежуточных результатов. Это может быть не так много, но более 10000 случаев это основной фактор, который сокращает время.

Я проверил этот код и передал все тестовые примеры. Вот измененный код:

#include <stdio.h> 

    const int MAX_LENGTH = 18; 
    const int MAX_SUM = 162; 
    const int MAX_SQUARE_SUM = 1458; 
    int primes[1460]; 
    unsigned long long dyn_table[20][164][1460]; 
    //changed here.......1 
    unsigned long long ans[19][10][164][1460]; //about 45 MB 

    int start[19][163]; 
    int end[19][163]; 
    //upto here.........1 
    void gen_primes() { 
     for (int i = 0; i <= MAX_SQUARE_SUM; ++i) { 
      primes[i] = 1; 
     } 
     primes[0] = primes[1] = 0; 

     for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i) { 
      if (!primes[i]) { 
       continue; 
      } 
      for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j) { 
       primes[i*j] = 0; 
      } 
     } 
    } 

    void gen_table() { 
     for (int i = 0; i <= MAX_LENGTH; ++i) { 
      for (int j = 0; j <= MAX_SUM; ++j) { 
       for (int k = 0; k <= MAX_SQUARE_SUM; ++k) { 
        dyn_table[i][j][k] = 0; 
       } 
      } 
     } 
     dyn_table[0][0][0] = 1; 

     for (int i = 0; i < MAX_LENGTH; ++i) { 
      for (int j = 0; j <= 9 * i; ++j) { 
       for (int k = 0; k <= 9 * 9 * i; ++k) { 
        for (int l = 0; l < 10; ++l) { 
         dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k]; 
        } 
       } 
      } 
     } 
    } 

    unsigned long long count_lucky (unsigned long long maxp) { 
     unsigned long long result = 0; 
     int len = 0; 
     int split_max[MAX_LENGTH]; 
     while (maxp) { 
      split_max[len] = maxp % 10; 
      maxp /= 10; 
      ++len; 
     } 
     int sum = 0; 
     int sq_sum = 0; 
     unsigned long long step_result; 
     unsigned long long step_; 
     for (int i = len-1; i >= 0; --i) { 
      step_result = 0; 
      int x1 = 9*i; 
      for (int l = 0; l < split_max[i]; ++l) { 
    //changed here........2 
       step_ = 0; 
       if(ans[i][l][sum][sq_sum]!=0) 
        { 
         step_result +=ans[i][l][sum][sq_sum]; 
         continue; 
        } 
       int y = l + sum; 
       int x = l*l + sq_sum; 
       for (int j = 0; j <= x1; ++j) { 
        if(primes[j + y]) 
         for (int k=start[i][j]; k<=end[i][j]; ++k) { 
          if (primes[k + x]) { 
           step_result += dyn_table[i][j][k]; 
           step_+=dyn_table[i][j][k]; 
          } 
        } 

       } 
       ans[i][l][sum][sq_sum] = step_; 
    //upto here...............2 
      } 
      result += step_result; 
      sum += split_max[i]; 
      sq_sum += split_max[i] * split_max[i]; 
     } 

     if (primes[sum] && primes[sq_sum]) { 
      ++result; 
     } 

     return result; 
    } 

    int main(int argc, char** argv) { 
     gen_primes(); 
     gen_table(); 

    //changed here..........3 
     for(int i=0;i<=18;i++) 
      for(int j=0;j<=163;j++) 
       { 
        for(int k=0;k<=1458;k++) 
          if(dyn_table[i][j][k]!=0ll) 
           { 
            start[i][j] = k; 
            break;        
           } 

        for(int k=1460;k>=0;k--) 
          if(dyn_table[i][j][k]!=0ll) 
           { 
            end[i][j]=k; 
            break;        
           } 
       } 
    //upto here..........3 
     int cases = 0; 
     scanf("%d",&cases); 
     for (int i = 0; i < cases; ++i) { 
      unsigned long long a, b; 

      scanf("%lld %lld", &a, &b); 
    //changed here......4 
      if(b == 1000000000000000000ll) 
       b--; 
    //upto here.........4 
      printf("%lld\n", count_lucky(b) - count_lucky(a-1)); 
     } 
     return 0; 

} 

Объяснение:

gen_primes() и gen_table() в значительной степени сами за себя.

count_lucky() работает следующим образом:

разделить число в split_max [], просто хранить единый значное число для единицы, десятки, сотни и т.д. позиций. Идея такова: предположим, что split_map [2] = 7, так что нам нужно вычислить результат для

1 в положение сотен и все 00 до 99.

2 в положение сотен и все 00 до 99.

. .

7 в положении сотни и все от 00 до 99.

это на самом деле сделано (в л цикле) в терминах суммы цифр и суммы квадрата цифр, которые были precalcutaled. для этого примера: сумма будет варьироваться от 0 до 9 * i & сумма квадрата будет варьироваться от 0 до 9 * 9 * i ... это делается в j и k петлях. Это повторяется для всех длин в цикле i

Это была идея OleGG.

Для оптимизации Ниже рассматривается:

  1. его бесполезно запускать сумму квадратов от 0 до 9 * 9 * я, как для конкретных сумм цифр он не будет идти Шифрование до полного диапазона. Например, если i = 3 и сумма равна 5, то сумма квадрата не будет меняться от 0 до 9 * 9 * 3. Эта часть хранится в массивах start [] и end [] с использованием предварительно вычисленных значений.

  2. Для запоминания запоминается значение для определенного количества цифр и определенной цифры в самом значительном положении числа и до определенной суммы и до определенной суммы квадрата. Слишком долго, но все же около 45 МБ. Я считаю, что это может быть дополнительно оптимизировано.

+1

Поздравляем, вы это сделали.Я знал, что трюк должен был хранить промежуточные результаты, и я даже имел правильное представление о том, как эти результаты будут представлять разбивку исходного номера (см. Мой ответ выше с примером «00000-54321»). Однако я не смог реализовать его с помощью массива [19] [10] [164] [1460], как вы это делали. Браво ... этот ответ заслуживает галочку и еще много оборотов. Sidenote: обман, который вы делаете с k, очень мало ... самые большие выигрыши исходят от 'ans []' (20x gain) и от перемещения 'if (primes [j + y])' из цикла (2x усиления) , В общей сложности ~ 40 раз. – The111

+0

Не обращайте внимания на мои комментарии «20x» и «2x» выше. Это только имеет смысл для определенного тестового примера, в котором я работал. Очевидно, это зависит от общего количества тестовых случаев. Несмотря на это, это все еще два самых больших улучшения. – The111

+1

InterviewStreet должен быть персональным вызовом! Предоставление консультаций и помощь в том, чтобы попасть на трассу - это одно, а размещение простого кода - другое ... – Benoit

0

Исходя из требований, вы можете сделать это по-разному. Если бы я это делал, я бы вычислил простые числа, используя «Сито Eratosthenes» в требуемом диапазоне (от A до (9 * 2) * B.length), кешируйте их (опять же, в зависимости от вашей установки, вы можете использовать в -memory или disk cache) и использовать его для следующего запуска.

Я просто закодированы быстрое решение (Java), как показано ниже (ПРИМЕЧАНИЕ:.. Целочисленное переполнение не проверяется просто быстрый пример Кроме того, мой код не оптимизирован.):

import java.util.ArrayList; 
import java.util.Arrays; 

public class LuckyNumbers { 
    public static void main(String[] args) { 
     int a = 0, b = 1000; 
     LuckyNumbers luckyNums = new LuckyNumbers(); 
     ArrayList<Integer> luckyList = luckyNums.findLuckyNums(a, b); 
     System.out.println(luckyList); 
    } 

    private ArrayList<Integer> findLuckyNums(int a, int b) { 
     ArrayList<Integer> luckyList = new ArrayList<Integer>(); 
     int size = ("" + b).length();   
     int maxNum = 81 * 4; //9*2*b.length() - 9 is used, coz it's the max digit 
     System.out.println("Size : " + size + " MaxNum : " + maxNum); 
     boolean[] primeArray = sieve(maxNum); 

     for(int i=a;i<=b;i++) { 
      String num = "" + i; 
      int sumDigits = 0; 
      int sumSquareDigits = 0; 

      for(int j=0;j<num.length();j++) { 
       int digit = Integer.valueOf("" + num.charAt(j)); 
       sumDigits += digit; 
       sumSquareDigits += Math.pow(digit, 2); 
      } 

      if(primeArray[sumDigits] && primeArray[sumSquareDigits]) { 
       luckyList.add(i); 
      } 
     } 

     return luckyList; 
    } 

    private boolean[] sieve(int n) { 
     boolean[] prime = new boolean[n + 1]; 
     Arrays.fill(prime, true); 
     prime[0] = false; 
     prime[1] = false; 
     int m = (int) Math.sqrt(n); 

     for (int i = 2; i <= m; i++) { 
      if (prime[i]) { 
       for (int k = i * i; k <= n; k += i) { 
        prime[k] = false; 
       } 
      } 
     } 

     return prime; 
    } 
} 

И выход:

[11, 12, 14, 16, 21, 23, 25, 32, 38, 41, 49, 52, 56, 58, 61, 65, 83, 85, 94, 101, 102 , 104, 106, 110, 111, 113, 119, 120, 131, 133, 137, 140, 146, 160, 164, 166, 173, 179, 191, 197, 199, 201, 203, , 229, 230, 232, 250, 289, 292, 298, 302, 308, 311, 313, 317, 320, 322, 331, 335, 337, 344, 346, 353, 355, 364, 36 8, 371, 373, 377, 379, 380, 386, 388, 397, 401, 409, 410, 416, 434, 436, 443, 449, 461, 463, 467, 476, 490, 494, 502, 506, 508, 520, 533, 535, 553, 559, 560, 566, 580, 595, 601, 605, 610, 614, 616, 634, 638, 641, 643, 647, 650, 656, 661, 665, 674, 683, 689, 698, 713, 719, 731, 733, 737, 739, 746, 764, 773, 779, 791, 793, 797, 803, 805, 829, 830, 836, 838, 850, 863, 869, 883, 892, 896, 904, 911, 917, 919, 922, 928, 937, 940, 944, 955, 968, 971, 973, 977, 982, 986, 991]

+3

К сожалению, ваш алгоритм будет все еще медленным, если (B - A) -> 10^18, потому что он по-прежнему линейный – OleGG

+1

В моей реализации я использовал метод Eratosthenes. Это тривиально. Главное, что нужно оптимизировать, - это перемещение всех чисел и вычисление суммы их цифр, потому что цифры варьируются от 1 до 10^18. – vgeta

+0

диапазон простых чисел на самом деле намного меньше –

0

Я не тщательно проанализировал ваше текущее решение, но это может улучшить его:

Поскольку порядок цифр не имеет значения, вы должны пройти все возможные комбинации цифр 0-9 от 1 до 18, отслеживая сумму цифр и их квадраты и добавление одной цифры в тим e, используя результат предыдущего расчета.

Так что, если вы знаете, что на 12 суммы цифр 3 и 5 квадратов, посмотрите на цифры 120, 121, 122 ... и т.д. и рассчитать суммы для них тривиальным из 3 и 5 для 12

+0

Алгоритм целочисленного раздела делает это. А также каждый раздел перестановочен. – vgeta

+1

Подождите, почему вам нужно переставить? Вы должны уметь вычислять количество перестановок по формуле? –

+0

взять 11 сумма 2 .. 110 и 101 также произвести ту же сумму. – vgeta

4

Вместо перечисления пространства чисел перечисляйте разные «подписи» чисел, которым повезло. а затем распечатать всю комбинацию differnet из них.

Это может быть сделан с тривиальными возвратами:

#define _GNU_SOURCE 
#include <assert.h> 
#include <limits.h> 
#include <stdbool.h> 
#include <stdint.h> 
#include <stdio.h> 

#define bitsizeof(e) (CHAR_BIT * sizeof(e)) 
#define countof(e)  (sizeof(e)/sizeof((e)[0])) 
#define BITMASK_NTH(type_t, n) (((type_t)1) << ((n) & (bitsizeof(type_t) - 1))) 
#define OP_BIT(bits, n, shift, op) \ 
    ((bits)[(unsigned)(n)/(shift)] op BITMASK_NTH(typeof(*(bits)), n)) 
#define TST_BIT(bits, n) OP_BIT(bits, n, bitsizeof(*(bits)), & ) 
#define SET_BIT(bits, n) (void)OP_BIT(bits, n, bitsizeof(*(bits)), |=) 

/* fast is_prime {{{ */ 

static uint32_t primes_below_1M[(1U << 20)/bitsizeof(uint32_t)]; 

static void compute_primes_below_1M(void) 
{ 
    SET_BIT(primes_below_1M, 0); 
    SET_BIT(primes_below_1M, 1); 
    for (uint32_t i = 2; i < bitsizeof(primes_below_1M); i++) { 
     if (TST_BIT(primes_below_1M, i)) 
      continue; 
     for (uint32_t j = i * 2; j < bitsizeof(primes_below_1M); j += i) { 
      SET_BIT(primes_below_1M, j); 
     } 
    } 
} 

static bool is_prime(uint64_t n) 
{ 
    assert (n < bitsizeof(primes_below_1M)); 
    return !TST_BIT(primes_below_1M, n); 
} 

/* }}} */ 

static uint32_t prime_checks, found; 

static char  sig[10]; 
static uint32_t sum, square_sum; 

static void backtrack(int startdigit, int ndigits, int maxdigit) 
{ 
    ndigits++; 

    for (int i = startdigit; i <= maxdigit; i++) { 
     sig[i]++; 
     sum  += i; 
     square_sum += i * i; 
     prime_checks++; 
     if (is_prime(sum) && is_prime(square_sum)) { 
       found++; 
     } 
     if (ndigits < 18) 
      backtrack(0, ndigits, i); 
     sig[i]--; 
     sum  -= i; 
     square_sum -= i * i; 
    } 
} 

int main(void) 
{ 
    compute_primes_below_1M(); 
    backtrack(1, 0, 9); 

    printf("did %d signature checks, found %d lucky signatures\n", 
      prime_checks, found); 
    return 0; 
} 

Когда я запускаю его делает:


$ time ./lucky 
did 13123091 signature checks, found 933553 lucky signatures 
./lucky 0.20s user 0.00s system 99% cpu 0.201 total 

Вместо найден ++ вы хотите создать все различные перестановки цифр, которые вы можете построить с этим номером. Я также прекомпопую первые 1М простых чисел.

Я не проверял правильность кода на 100%, возможно, вам придется немного отладить его. Но здесь есть шероховатая идея, и я могу сгенерировать всю удачную перестановку ниже 0,2 с (даже без ошибок она не должна быть более чем в два раза медленнее).

И, конечно, вы хотите сгенерировать перестановки, которые проверяют A < = B. Возможно, вы захотите проигнорировать генерации разделов, у которых больше цифр, чем B или меньше A. В любом случае, вы можете улучшить мою общую идею отсюда.

(Примечание: аннотация на старте, потому что я вырезал и код пасты я написал для Project Euler, следовательно, очень быстрого is_prime, который работает на N < = 1M;))

+0

Да, я думаю, это то, о чем я говорю. –

14

Вы должны использовать DP для этого задача. Вот мое решение:

#include <stdio.h> 

const int MAX_LENGTH = 18; 
const int MAX_SUM = 162; 
const int MAX_SQUARE_SUM = 1458; 
int primes[1459]; 
long long dyn_table[19][163][1459]; 

void gen_primes() { 
    for (int i = 0; i <= MAX_SQUARE_SUM; ++i) { 
     primes[i] = 1; 
    } 
    primes[0] = primes[1] = 0; 

    for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i) { 
     if (!primes[i]) { 
      continue; 
     } 
     for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j) { 
      primes[i*j] = 0; 
     } 
    } 
} 

void gen_table() { 
    for (int i = 0; i <= MAX_LENGTH; ++i) { 
     for (int j = 0; j <= MAX_SUM; ++j) { 
      for (int k = 0; k <= MAX_SQUARE_SUM; ++k) { 
       dyn_table[i][j][k] = 0; 
      } 
     } 
    } 
    dyn_table[0][0][0] = 1; 

    for (int i = 0; i < MAX_LENGTH; ++i) { 
     for (int j = 0; j <= 9 * i; ++j) { 
      for (int k = 0; k <= 9 * 9 * i; ++k) { 
       for (int l = 0; l < 10; ++l) { 
        dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k]; 
       } 
      } 
     } 
    } 
} 

long long count_lucky (long long max) { 
      long long result = 0; 
    int len = 0; 
    int split_max[MAX_LENGTH]; 
    while (max) { 
     split_max[len] = max % 10; 
     max /= 10; 
     ++len; 
    } 
    int sum = 0; 
    int sq_sum = 0; 
    for (int i = len-1; i >= 0; --i) { 
     long long step_result = 0; 
     for (int l = 0; l < split_max[i]; ++l) { 
      for (int j = 0; j <= 9 * i; ++j) { 
       for (int k = 0; k <= 9 * 9 * i; ++k) { 
        if (primes[j + l + sum] && primes[k + l*l + sq_sum]) { 
         step_result += dyn_table[i][j][k]; 
        } 
       } 
      } 
     } 
     result += step_result; 

     sum += split_max[i]; 
     sq_sum += split_max[i] * split_max[i]; 
    } 

    if (primes[sum] && primes[sq_sum]) { 
     ++result; 
    } 

    return result; 
} 

int main(int argc, char** argv) { 
    gen_primes(); 
    gen_table(); 

    int cases = 0; 
    scanf("%d", &cases); 
    for (int i = 0; i < cases; ++i) { 
     long long a, b; 
     scanf("%lld %lld", &a, &b); 
     printf("%lld\n", count_lucky(b) - count_lucky(a-1)); 
    } 
    return 0; 
} 

Краткое объяснение:

  • Я вычисление всех простых чисел до 9 * 9 * MAX_LENGTH используя метод Эратосфена;
  • Позже, используя DP, я создаю таблицу dyn_table где значение X в dyn_table [я] [J] [к] означает, что мы имеем ровно X чисел длины I с суммой цифр равно J и суммой квадратов его равным K
  • Тогда мы можем легко подсчитать количество счастливых чисел от 1 до 999..999 (Len времен 9). Для этого мы просто суммируем все dyn_table [len] [j] [k], где оба j и k - простые числа.
  • Чтобы рассчитать количество счастливого числа от 1 до случайного Х мы разделили интервал от 1 до Х на интервалы с длиной, равной 10^K (см * * count_lucky функции).
  • И наш последний шаг - вычесть count_lucky (a-1) (потому что мы включаем a в наш интервал) от count_lucky (b).

Это все. Предварительная калькуляция для O (log (MAX_NUMBER)^3), каждый шаг также имеет такую ​​сложность.

Я проверил мое решение против линейного прямых один и результатов были равны

+1

Это очень умное решение. К сожалению, он не работает на 100%, так как вы не можете разделить каждое число на десять. У меня было другое решение, которое также требовало подобного разделения, и не могло найти никакого способа разбить число, например 1234567, хотя 1100111 будет разделяться штрафом в степени 10. Большинство чисел разбивается на * кратные * степеней 10, которые не совсем работают с вашей таблицей, я не думаю. – The111

+1

Благодарим вас за комментарий, я действительно нашел одну ошибку в своем algo, поэтому я ее обновил. Теперь я тестировал его по всем номерам от 1 до миллиона, и результаты были одинаковыми с простым решением. Цикл 'for (int l = 0; l OleGG

+0

Спасибо за обновление. Я сам пытался придумать подобный мод для вашего алгоритма, но ваш лучше. :-) – The111

0

Иногда быстрое решение невероятно просто:

uint8_t precomputedBitField[] = { 
    ... 
}; 

bool is_lucky(int number) { 
    return precomputedBitField[number >> 8] & (1 << (number & 7)); 
} 

просто изменить существующий код для создания «precomputedBitField».

Если вы беспокоитесь о размере, чтобы покрыть все числа от 0 до 999, это будет стоить вам всего 125 байт, поэтому этот метод, вероятно, будет меньше (и намного быстрее), чем любая другая альтернатива.

+0

Побитовые операции ничего не стоят по сравнению со всей сложностью алгоритма. Кроме того, массив простых чисел может легко лежать в кеш процессора и получать число от него будет даже быстрее, чем выполнение операций над массивом – OleGG

+1

Прошу прощения. Я не могу понять это решение. Не могли бы вы рассказать об этом еще немного. Спасибо – vgeta

+0

Это не новое решение, а изменение существующего, чтобы массив простых чисел лучше вписывался в память. Для нашего случая это было бы неважно – OleGG

-2

Прежде всего я хотел бы добавить, что счастливое число можно вычислить с помощью сита, объяснения сита можно найти здесь: http://en.wikipedia.org/wiki/Lucky_number

так что вы можете улучшить вашу скорость решения с использованием сита для определения номера ,

+0

Я не думаю, что «Счастливые числа», созданные ситом, - это «счастливые числа», которые я ищу. «Число называется счастливым, если сумма его цифр, а также сумма квадратов его цифр - это простое число» – vgeta

3

Для тех, кто не знал об этом, это проблема на сайте InterviewStreet.com (и, на мой взгляд, самая сложная там). Мой подход начался аналогично (и был вдохновлен) OleGG ниже. Однако, создав первую таблицу [19] [163] [1459], которую он сделал (которую я назвал table1), я пошел в несколько другом направлении. Я создал вторую таблицу оборванной длины [19] [x] [3] (таблица2), где x - количество уникальных пар сумм для соответствующего количества цифр. И для третьего измерения таблицы, с длиной 3, 1-й элемент представляет собой количество уникальных «суммарных пар» с суммами и значениями squareSum, удерживаемыми 2-м и 3-м элементами.

Например:

//pseudocode 

table2[1] = new long[10][3] 
table2[1] = {{1, 0, 0}, {1, 1, 1}, {1, 2, 4}, 
      {1, 3, 9}, {1, 4, 16}, {1, 5, 25}, 
      {1, 6, 36}, {1, 7, 49}, {1, 8, 64}, {1, 9, 81}} 

table2[2] = new long[55][3] 
table2[3] = new long[204][3] 
table2[4] = new long[518][3] 
    . 
    . 
    . 
    . 
table2[17] = new long[15552][3] 
table2[18] = new long[17547][3] 

Цифры у меня на второй длине размерности массива (10, 55, 204, 518, ..., 15552, 17547) может быть проверена путем запроса table1, и аналогичным образом таблица2 может быть заполнена. Теперь, используя таблицу 2, мы можем решить большие «удачливые» запросы намного быстрее, чем метод OleGG, но все же использует подобный «расколотый» процесс, как и он. Например, если вам нужно найти повезло (00000-54321) (т.е. счастливые числа от 0 до 54321), он распадается на сумму следующих 5 строк:

lucky(00000-54321) = { 
    lucky(00000-49999) + 
    lucky(50000-53999) + 
    lucky(54000-54299) + 
    lucky(54300-53319) + 
    lucky(54320-54321) 
} 

который расщепляет дальше:

lucky(00000-49999) = { 
    lucky(00000-09999) + 
    lucky(10000-19999) + 
    lucky(20000-29999) + 
    lucky(30000-39999) + 
    lucky(40000-49999) 
} 
    . 
    . 
lucky(54000-54299) = { 
    lucky(54000-54099) + 
    lucky(54100-54199) + 
    lucky(54200-54299) 
} 
    . 
    . 
    . 
    etc 

Каждое из этих значений можно легко получить, запросив таблицу2. Например, к счастью (40000-49999) определяется путем добавления 4 и 16 на 2-й и 3-й элементов третьей размерности table2:

sum = 0 
for (i = 0; i < 518; i++) 
    if (isPrime[table2[4][i][1] + 4] && isPrime[table2[4][i][2] + 4*4]) 
     sum += table2[4][i][0] 
return sum 

или для удачливы (54200-54299):

sum = 0 
for (i = 0; i < 55; i++) 
    if (isPrime[table2[2][i][1] + (5+4+2)] 
    && isPrime[table2[2][i][2] + (5*5+4*4+2*2)]) 
     sum += table2[2][i][0] 
return sum 

Теперь решение OleGG выполнялось значительно быстрее, чем все, что я пробовал до тех пор, но с моими модификациями, описанными выше, он работает даже лучше, чем раньше (примерно в 100 раз для большого набора тестов). Тем не менее, он все еще не достаточно быстро подходит для слепых тестов, которые даются на интервью. Через какой-то умный взлом я смог определить, что я в настоящее время работает около 20 раз слишком медленно, чтобы завершить свой тестовый набор в назначенное время. Однако я не могу найти дальнейших оптимизаций.Самое большое время здесь - это, очевидно, повторение второго измерения таблицы2, и единственным способом избежать этого было бы суммировать результаты этих сумм. Однако есть слишком много возможностей вычислить их все за указанное время (5 секунд) или сохранить их все в указанном пространстве (256 МБ). Например, удачный (54200-54299) цикл выше может быть предварительно вычислен и сохранен как одно значение, но если бы это было так, нам также нужно было предварительно вычислить удачу (123000200-123000299) и повезло (99999200-99999299)) и т. д. Я сделал математику, и это слишком много вычислений для предварительной вычисления.

0

Я пытался придумать решение, используя метод перечисления Пьера, но никогда не придумал достаточно быстрый способ подсчета перестановок. Метод подсчета OleGG очень умный, и оптимизация пиратов необходима, чтобы сделать его достаточно быстрым. Я придумал одно небольшое улучшение, и один обходной путь к серьезной проблеме.

Во-первых, улучшение: вам не нужно проходить через все суммы и квадраты один за другим, проверяя простые числа в пиратских j и k петлях. Вы имеете (или можете легко генерировать) список простых чисел. Если вы используете другие переменные, чтобы выяснить, какие простые числа находятся в диапазоне, вы можете просто перечислить список подходящих простых чисел для суммы и квадратов. Массив простых чисел и таблица поиска, чтобы быстро определить, при каком индексе простое число = = число является полезным. Однако это, вероятно, лишь незначительное улучшение.

Большая проблема связана с массивом кеша пирата. Это не 45 МБ, как утверждается; с 64-битными вводами, это что-то вроде 364 МБ. Это за пределами допустимых пределов памяти для C и Java. Его можно уменьшить до 37 МБ, избавившись от измерения «l», что не является необходимым и в любом случае ухудшает производительность кеша. Вы действительно заинтересованы в подсчете кеширования для l + sum и l * l + squaresum, а не l, sum и squaresum индивидуально.

1

Я только что решил эту проблему.

Это просто проблема динамического программирования. Возьмите DP[n](sum-square_sum) в качестве функции DP, а DP[n](sum-square_sum) - это счетчик всех чисел, цифры которых меньше или равны n, а сумма и квадрат цифр цифр обозначаются соответственно sum и square_sum. Например:

DP[1](1-1) = 1  # only 1 satisfies the condition       
DP[2](1-1) = 2  # both 1 and 10 satisfies the condition       
DP[3](1-1) = 3  # 1 10 100 
DP[3](2-4) = 3  # 11 110 101 

Так как мы можем легко вычислить первое состояние DP DP [1] [..] [..], это:

(0-0) => 1  (1-1) => 1 (2-4) => 1  (3-9) => 1  (4-16) => 1  
(5-25) => 1 (6-36) => 1 (7-49) => 1 (8-64) => 1 (9-81) => 1 

тогда мы можем сделать вывод, DP [1 ] из DP [1], а затем DP [3] ... DP [18] выведенное выше сделано тем, что каждый раз, когда n увеличивается на 1, например, от DP [1] до DP [2] , мы получили новую цифру (0..9), а набор (sum, square_sum) пары (т.е. DP [n]) должен быть обновлен.

Наконец, мы можем пересечь набор DP [18] и подсчитать числа, которым повезло.

Ну, а как насчет сложности времени и пространства алгоритма выше? Как известно, сумма < = 18 * 9 = 162, square_sum < = 18 * 9 * 9 = 1458, поэтому набор пары (сумма, квадрат_сум) (т.е. DP [n]) очень мал, меньше 162 * 1458 = 236196, на самом деле он намного меньше, чем 236196; Дело в том, что моя рубиновая программа, подсчитывающая все счастливые числа от 0 до 10^18, заканчивается менее чем за 1 секунду.

ruby lucky_numbers.rb 0.55s user 0.00s system 99% cpu 0.556 total 

и я проверить свою программу, написав функцию тестирования с использованием алгоритма грубой силы, и это верно для чисел меньше 10^7.

Смежные вопросы