Три цвета треугольников

72
7

Я пытаюсь сделать код для этой проблемы:

(Источник: https://www.codewars.com/kata/insane-coloured-triangles/train/c)

Цветной треугольник создается из ряда цветов, каждый из которых является красным, зеленым или синим. Последовательные строки, каждая из которых содержит один меньший цвет, чем последний, генерируются путем рассмотрения двух трогательных цветов в предыдущей строке. Если эти цвета идентичны, тот же цвет используется в новой строке. Если они отличаются, недостающий цвет используется в новой строке. Это продолжается до тех пор, пока не будет создана окончательная строка с единственным цветом.

Например, различные возможности:

Colour here:            G G        B G        R G        B R 
Becomes colour here: G R B G

With a bigger example:
R R G B R G B B
R B R G B R B
G G B R G G
G R G B G
B B R R
B G R
R B
G

Вам будет предоставлена первая строка треугольника в виде строки и ее задание, чтобы вернуть окончательный цвет, который будет отображаться в нижней строке в виде строки. В случае вышеприведенного примера вам будет предоставлен 'RRGBRGBB', и вы должны вернуть 'G'.

Ограничения:

1 <= length(row) <= 10 ** 5

Входная строка будет содержать только прописные буквы " B', 'G' or 'R'.

Точное количество тестовых случаев будет следующим:

100 tests of 100 <= length(row) <= 1000 
100 tests of 1000 <= length(row) <= 10000
100 tests of 10000 <= length(row) <= 100000

Примеры:

triangle('B') == 'B'
triangle('GB') == 'R'
triangle('RRR') == 'R'
triangle('RGBG') == 'B'
triangle('RBRGBRB') == 'G'
triangle('RBRGBRBGGRRRBGBBBGG') == 'G'

Таким образом, я сделал этот код, и он работает для всех образцов вкусов, но когда дело доходит до length of row > 25 он терпит неудачу из-за моей факториальной функции, а длина в некоторых случаях составляет до 100,000, поэтому любое предложение по устранению этой проблемы при наименее любая математическая формула, которая может решить проблему или небольшой намек.

кстати, я сделал этот код после того, как нашел математический способ решить проблему на этом сайте:

https://math.stackexchange.com/questions/2257158/three-color-triangle-challenge

long long factorial(long long num)     
{
long long fact = num;

if (fact == 0)
fact = 1;

while (num > 1)
{
num--;
fact *= num;
}
return (fact);
}

long long combination(long long n, long long k, long long fact_n)
{
long long fact_k = factorial(k);
long long comb = ( fact_n / (fact_k * factorial(n - k)) );
return (comb);
}

char triangle(const char *row)
{
long long len = strlen(row);
long long n = len - 1;
long long k = 0;
int sign = pow(-1,n);
long long sum = 0;
char *result = "RGB";
int a;
long long fact_n = factorial(n);

while (k < len) //This while calculate Segma (∑).
{
if (row[k] == 'R')
a = 0;
else if (row[k] == 'G')
a = 1;
else if (row[k] == 'B')
a = 2;

if (a != 0)
sum = sum + (combination(n,k,fact_n) * a);
k++;
}

sum = sign * (sum % 3);

if (sum == -1 || sum == -2)
sum += 3;

return (result[sum]);
}

спросил(а) 2018-12-03T01:02:00+03:00 1 год, 4 месяца назад
1
Решение
75

Я буду считать, что формула в приведенной вами ссылке верна:

enter image description here

Чтобы избежать целочисленного переполнения, нам нужно будет применить эти правила по модулю арифметики:

(a * b) mod c = ((a mod c) * (b mod c)) mod c

(a ± b) mod c = ((a mod c) ± (b mod c)) mod c

Применяя их к формуле:

enter image description here

Но как мы вычисляем

enter image description here

... без прямого вычисления самого коэффициента (что может вызвать переполнение)?

Так как 3 - простое число, это может быть выполнено с помощью теоремы Лукаса:

enter image description here

... где n_i, m_i - i -th цифры n, m в базе-3.

Преобразование в base-3 легко с целым делением:

// convert a number to base 3
// and returns the number of digits
unsigned conv_base_3(unsigned n, unsigned max, unsigned* out)
{
unsigned i = 0;
while (i < max && n > 0)
{
out[i] = n % 3;
n /= 3;
i++;
}
return i;
}

Заметим, что поскольку n_i, m_i всегда находятся в диапазоне [0, 2] (потому что они являются базовыми цифрами 3), C(n_i, m_i) очень легко вычислить:

// calculate the binomial coefficient for n < 3
unsigned binom_max_2(unsigned n, unsigned k)
{
if (n < k)
return 0;
switch (n)
{
case 0:
case 1:
return 1;
case 2:
return 1 + (k == 1);

// shouldn't happen
default:
return 0;
}
}

И теперь сама теорема:

// Lucas theorem for p = 3
unsigned lucas_3(
unsigned len_n, const unsigned * dig_n,
unsigned len_k, const unsigned * dig_k
)
{
// use modulo product rule:
// prod[i] % 3 = ((prod[i - 1] % 3) * value[i])
unsigned prod = 1;
for (unsigned i = 0; i < len_n; i++) {
unsigned n_i = dig_n[i];
unsigned k_i = (i < len_k) ? dig_k[i] : 0;
prod = (prod * binom_max_2(n_i, k_i)) % 3;
}
return prod % 3;
}

Преобразование символов:

// convert from 012 to RGB
char int_2_char(int i)
{
switch (i) {
case 0: return 'R';
case 1: return 'G';
case 2: return 'B';

// shouldn't happen
default:
return '\0';
}
}

// convert from RGB to 012
unsigned char_2_int(char c)
{
switch (c) {
case 'R': return 0;
case 'G': return 1;
case 'B': return 2;

// shouldn't happen
default:
return 3;
}
}

Наконец, алгоритм треугольника:

// the problem constraints state that n <= 10 ** 5
// max number of base-3 digits
#define MAX_N_LOG_3 11

// main algorithm function
char triangle(const char * input)
{
unsigned sum = 0;
const int n = strlen(input);

// calculate digits of n - 1
unsigned dig_n[MAX_N_LOG_3];
unsigned len_n = conv_base_3(n - 1, MAX_N_LOG_3, dig_n);

for (unsigned km1 = 0; km1 < n; km1++)
{
// calculate digits of k - 1
unsigned dig_k[MAX_N_LOG_3];
unsigned len_k = conv_base_3(km1, MAX_N_LOG_3, dig_k);

// calculate C(n - 1, k - 1) mod 3
unsigned Cnk_mod3 = lucas_3(len_n, dig_n, len_k, dig_k);

// add using the modulo rule
sum = (sum + Cnk_mod3 * char_2_int(input[km1])) % 3;
}

// value of (-1) ** (n - 1)
// (no need for pow; just need to know if n is odd or even)
int sign = (n % 2) * 2 - 1;

// for negative numbers, must resolve the difference
// between C % operator and mathematical mod
int sum_mod3 = (3 + (sign * (int)(sum % 3)) % 3;
return int_2_char(sum_mod3);
}

Вышеприведенный код передает все тесты; обратите внимание, что это было написано в пользу ясности, а не производительности.

Итак, почему этот код смог пройти все тесты в назначенное время, тогда как простой подход, основанный на таблицах, не был таким? Из-за его временной сложности:

    Табличный подход обрабатывает все уровни треугольника, который равен O(n^2) (см. Номера треугольников).

    Конечно, используя алгоритм Лукаса, нужно обработать только верхний уровень; однако сам алгоритм O(log n), поскольку он проходит через каждую цифру n (независимо от базы). Общая сложность - O(n log n), которая по-прежнему представляет собой значительное улучшение.

ответил(а) 2018-12-03T15:13:00+03:00 1 год, 4 месяца назад
36

Рекурсия здесь мало используется (а), так как вам никогда не придется возвращаться в предыдущее состояние. Итерация - лучшее решение и может быть сделано довольно просто.

Сначала получите начальную строку цветов и создайте другой один размер.

Затем для каждого набора из двух символов (перекрывающихся) в оригинале установите эквивалентный символ в новой строке на основе ваших правил. Наконец, как только новая строка будет полностью сформирована (на один символ короче оригинала), скопируйте ее обратно в оригинал и, если только один символ не будет длинным, продолжайте движение (вернитесь к началу этого параграфа).

Вот пример кода, который показывает это в действии:

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

// Helper function to output nicely spaced string.

static void output(int gap, char *str) {
// Spaces at line start.

for (int i = 0; i < gap; ++i)
putchar(' ');

// Actual string with spaces between letters, end line following.

for (int i = 0, len = strlen(str); i < len; ++i)
printf(" %c", str[i]);
putchar('\n');
}

// The meat of the solution.

int main(int argc, char *argv[]) {
// Check parameter count and content.

if (argc != 2) {
fprintf(stderr, "*** Needs one argument\n");
return 1;
}

for (int i = 0, len = strlen(argv[1]); i < len; ++i) {
if (strchr("RGB", argv[1][i]) == NULL) {
fprintf(stderr, "*** Bad character '%c' found\n", argv[1][i]);
return 1;
}
}

// Allocate strings with check, prepare first.

char *first = malloc(strlen(argv[1]) + 1);
char *second = malloc(strlen(argv[1]) + 1);
if (first == NULL || second == NULL) {
fprintf(stderr, "*** Memory allocation error\n");
free (first);
free (second);
return 1;
}
strcpy(first, argv[1]);

// Continue until you have a short enough string.

int spaces = 0;
while (strlen(first) > 1) {
// Output current string, update spaces for next.

output(spaces++, first);

// Process each overlapping pair in string.

for (int i = 0, len = strlen(first); i < len - 1; ++i) {
// Select what goes in new string, based on pair.

char newCh = '?';

if (first[i] == 'R' && first[i+1] == 'R') newCh = 'R';
if (first[i] == 'G' && first[i+1] == 'G') newCh = 'G';
if (first[i] == 'B' && first[i+1] == 'B') newCh = 'B';

if (first[i] == 'G' && first[i+1] == 'R') newCh = 'B';
if (first[i] == 'R' && first[i+1] == 'G') newCh = 'B';

if (first[i] == 'B' && first[i+1] == 'G') newCh = 'R';
if (first[i] == 'G' && first[i+1] == 'B') newCh = 'R';

if (first[i] == 'B' && first[i+1] == 'R') newCh = 'G';
if (first[i] == 'R' && first[i+1] == 'B') newCh = 'G';

// Sanity check, should never be true.

if (newCh == '?') {
fprintf(stderr, "*** Bad data %c/%c\n", first[i], first[i+1]);
free (first);
free (second);
return 1;
}

// Place in new string and add terminator.

second[i] = newCh;
second[i+1] = '\0';
}

// Transfer second back to first for next cycle.

strcpy(first, second);
}

// Output final one-character string, clean up.

output(spaces, first);

free (first);
free (second);
return 0;
}

Вывод этого кода при запуске с аргументом RRGBRGBB, как и ожидалось,

 R R G B R G B B
R B R G B R B
G G B R G G
G R G B G
B B R R
B G R
R B
G

Он также работает с:

RRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBBRRGBRGBB

который значительно длиннее 25 символов, но я не буду показывать результат, поскольку он ужасно широк :-)

(a) Честно говоря (и я не пытаюсь быть излишне критическим здесь), все использование математики/факториалов также сомнительно. Я не уверен, почему вы считали, что это необходимо для того, что в основе проблемы обработки текста.

ответил(а) 2018-12-03T08:48:00+03:00 1 год, 4 месяца назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема