Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2015.09.10;
Скачать: CL | DM;

Вниз

Задачка для разминки мозга   Найти похожие ветки 

 
Rouse_ ©   (2014-12-25 21:12) [0]

Пришлось тут на днях решать для студента (первого курса) задачку.
Дословно выглядит вот так:

2014 ACM-ICPC China Hunan Invitational Programming Contest

There is a simple problem. Given a number N.
You are going to calculate N%1+ N%2+ N%3+...+ N%N.

Input: The length N(1<=N<=10^12).

Output: Answer.

Sample input  5
Sample output 4


Вроде ничего сложного, однако-ж результат суммирования по модулю явно не уложится ни в один из поддерживаемых типов (int64 vfrcbvev 8 байт).

Сразу уточню: задачка олимпиадная, причем свежего 14-го года :)

Конечно странно что такие вещи задают первокурснику, но...

В итоге для первого приближения накидал такое решение:

// pk04_dop.cpp : Defines the entry point for the console application.
//

// вообще-то это олимпиадная задача, я хз зачем ее студенту первого курса дают, но раз задали - значит задали

#include "stdafx.h"

// хранилище для оооочень большого числа
unsigned char UInt128[16];

void printUInt128(){
printf("result = 0x");
for (int i = 15; i >= 0; i--)
 printf("%hhX", UInt128[i]);
printf("\n");
}

// полный битовый сумматор реализующий таблицу истинности: http://ivatv.narod.ru/zifrovaja_texnika/1_04.htm
bool fullAdder(bool a, bool b, bool p0, bool *p){
// первый полусумматор
bool bRes = false;
*p = false;
if ((a && !b) || (b && !a))
 bRes = true;
if (a && b)
 *p = true;
if (!p0)
 return bRes;
// второй полусумматор
*p = true;
bRes = !bRes;
if (!a && !b)
 *p = false;
return bRes;
}

// сумматор на битовых операциях
void add(long long x){
bool pFlag = false;
unsigned long long halfResult = 0;
unsigned long long bigValue;
int i;
bool aBit, bBit, sFlag;

// получаем указатель на массив, содержащий большое число
unsigned long long *p;
p = (unsigned long long*)&UInt128[0];

// берем младшие 8 байт
bigValue = (unsigned long long)(*p);
for (i = 0; i < 64; i++){
 // и побитно, посредством полного сумматора складываем два числа
 aBit = ((unsigned long long)1 << i & x) > 0;
 bBit = ((unsigned long long)1 << i & bigValue) > 0;
 sFlag = fullAdder(aBit, bBit, pFlag, &pFlag);
 if (sFlag)
  halfResult |= (unsigned long long)1 << i;
};
// результат помещаем обратно
*p = halfResult;

// если нет переноса бит от предыдущей операции, то можно выходить
if (!pFlag)
 return;

halfResult = 0;

// сдвигаемся на 8 байт
p++;

// берем старшие 8 байт
bigValue = (unsigned long long)(*p);
for (i = 0; i < 64; i++){
 // увеличиваем значение описаясь на бит переноса
 bBit = ((unsigned long long)1 << i & bigValue) > 0;
 sFlag = fullAdder(false, bBit, pFlag, &pFlag);
 if (sFlag)
  halfResult |= (unsigned long long)1 << i;
};
// результат помещаем обратно
*p = halfResult;
}

int _tmain(int argc, _TCHAR* argv[])
{
unsigned long long a, i;

printf("enter value: ");
scanf("%lld", &a);

printf("calculating...\n");
i = a;
while(i > 0){
 add(a % i);
 i--;
};
printUInt128();

getchar();
getchar();

return 0;
}


Сразу скажу - ЭТО работает, причем работает правильно (можно даже использовать для проверки), однако есть более грамотный и более быстрый вариант решения в 17 строчек кода (на дельфи или на си - кому как удобнее) :)


 
Kerk ©   (2014-12-25 21:26) [1]


> Вроде ничего сложного, однако-ж результат суммирования по
> модулю явно не уложится ни в один из поддерживаемых типов
> (int64 vfrcbvev 8 байт).

Не уверен.
Максимальное значение Int64 сильно больше, чем 10^12. При этом ни один из остатков не может превысить 10^2 / 2.

Эта задача скорее на знание чего-нибудь хитрого из теории чисел, имхо.


 
Kerk ©   (2014-12-25 21:26) [2]


> не может превысить 10^12 / 2.

единицу потерял


 
Rouse_ ©   (2014-12-25 21:27) [3]

Вот так будет на дельфи, если у кого затруднения:

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
 Windows,
 System.SysUtils,
 Math;

type
 Int128 = array [0..15] of Byte;

var
 VeriBigInteger: Int128;

procedure PrintInt128;
var
 I: Integer;
begin
 Write("0x");
 for I := 15 downto 0 do
   Write(IntToHex(VeriBigInteger[I], 2));
 Writeln;
end;

function FullAdder(A, B, P0: Boolean; var P: Boolean): Boolean;
begin
 Result := False;
 P := False;
 if A and not B then
   Result := True;
 if B and not A then
   Result := True;
 if A and B then
   P := True;
 if not P0 then Exit;
 P := True;
 Result := not Result;
 if not A and not B then
   P := False;
end;

procedure Add(Value: Int64);
var
I: Integer;
ABit, BBit, SFlag, PFlag: Boolean;
HalfResult, BigValue: Int64;
begin
PFlag := False;
HalfResult := 0;
BigValue := PInt64(@VeriBigInteger[0])^;
for I := 0 to 63 do
begin
  ABit := (Int64(1) shl I and Value) > 0;
  BBit := (Int64(1) shl I and BigValue) > 0;
  SFlag := FullAdder(ABit, BBit, PFlag, PFlag);
  HalfResult := HalfResult or (Int64(SFlag) shl I);
end;
PInt64(@VeriBigInteger[0])^ := HalfResult;

HalfResult := 0;
BigValue := PInt64(@VeriBigInteger[8])^;
for I := 0 to 63 do
begin
  BBit := (1 shl I and BigValue) > 0;
  SFlag := FullAdder(False, BBit, PFlag, PFlag);
  HalfResult := HalfResult or (Byte(SFlag) shl I);
end;

PInt64(@VeriBigInteger[8])^ := HalfResult;
end;

function TstNative(X: int64): int64;
var
 I: Int64;
begin
 I := X;
 Result := 0;
 while I > 0 do
 begin
   Result := Result + (X mod I);
   Dec(I);
 end;
end;

procedure TstOld(X: int64);
var
 I: Int64;
begin
 ZeroMemory(@VeriBigInteger[0], SizeOf(Int128));
 I := X;
 while I > 0 do
 begin
   Add(X mod I);
   Dec(I);
 end;
 PrintInt128;
end;

var
 N: int64;
begin
 try
 repeat
   Readln(N);
   Writeln("0x", IntToHex(TstNative(N), 32));
   TstOld(N);
 until N = 0;
 except
   on E: Exception do
     Writeln(E.ClassName, ": ", E.Message);
 end;
 readln;
end.


 
Rouse_ ©   (2014-12-25 21:28) [4]


> Не уверен.
> Максимальное значение Int64 сильно больше, чем 10^12. При
> этом ни один из остатков не может превысить 10^2 / 2.

Так этож входное значение, а нужно просчитать сумму всех чисел по модулю yfxbyfz jn Т до нуля. Она там за глаза выходит за Int64


 
Inovet ©   (2014-12-25 21:35) [5]

А зачем это делать побитно? Пожно же сразу складывать младшие половины и потом старшие с учётом переноса. Перенос заполучить или из регистра состояния, или как-нибудь сравнением исходных чисел сделать.


 
Kilkennycat ©   (2014-12-25 21:41) [6]

...N%1...
a % что означает?


 
Inovet ©   (2014-12-25 21:42) [7]

Ну и это... в цикле гнать всю сумму как-то тупо. Здесь должно решаться математически, типа получается сумма целых от 1 до N, а это чё там такое N*(N-1)/2 что ли? Как-то просто слишком, а ну так длинное же и не влазит никуда.


 
Inovet ©   (2014-12-25 21:44) [8]

> [6] Kilkennycat ©   (25.12.14 21:41)
> a % что означает?

Это деление по модулю


 
Rouse_ ©   (2014-12-25 21:53) [9]


> a % что означает?

аналог MOD


 
Rouse_ ©   (2014-12-25 21:55) [10]


> а это чё там такое N*(N-1)/2

Откуда ты это взял? Нет там такого. Там есть диапазон от 0 до 10 в 12-ой степени, в пределах которого мыжет быть N


 
Kerk ©   (2014-12-25 22:13) [11]


> Так этож входное значение, а нужно просчитать сумму всех
> чисел по модулю yfxbyfz jn Т до нуля. Она там за глаза выходит
> за Int64

Максимальное значение суммы какое получается? Я в четверг вечером не готов посчитать :)

В целом, надо с другой стороны заходить. Олимпиады так в лоб не работают. Я запустил твою считалку полчаса назад, она до сих пор считает.

Например, можно разбить ряд на две части.

Одна часть будет
N%1 + N%2+ N%3 + ... + N%(N / 2)

Вторая часть
N%(N / 2 + 1) + N%(N / 2 + 1) + ... + N%N

Сумма второй части считается легко. Это банальная арифметическая прогрессия X*(X+1)/2, где X = ((N - 1) div 2).

То есть вторую часть можно вычислить вообще мгновенно. Над первой часть нужно дальше думать, искать способы. Возможно, тем же самым способом индуктивно можно пойти. Но я не готов сейчас глубже копать. Может быть, завтра.


 
MBo ©   (2014-12-25 22:14) [12]

>Откуда ты это взял? Нет там такого
Похожее есть. Сумма второй половины слагаемых Sh находится нетрудно без сложения.
M = (N - 1) div 2
Sh = M * (M + 1) / 2


 
Inovet ©   (2014-12-25 22:15) [13]

> [10] Rouse_ ©   (25.12.14 21:55)
> Нет там такого.

Ну значит я не понял вот это
N%1+ N%2+ N%3+...+ N%N


 
Rouse_ ©   (2014-12-25 22:19) [14]


> Максимальное значение суммы какое получается? Я в четверг
> вечером не готов посчитать :)
>
> В целом, надо с другой стороны заходить. Олимпиады так в
> лоб не работают. Я запустил твою считалку полчаса назад,
>  она до сих пор считает.

Ага, у меня тоже, даже оптимизированный вариант, но уже значение вышло за диапазон 8 байт :)


> MBo ©   (25.12.14 22:14) [12]
> >Откуда ты это взял? Нет там такого
> Похожее есть. Сумма второй половины слагаемых Sh находится
> нетрудно без сложения.
> M = (N - 1) div 2
> Sh = M * (M + 1) / 2

Ну что-то примерно похожее: 12499999999999999999999999,875


 
Kerk ©   (2014-12-25 22:21) [15]


> X*(X+1)/2, где X = ((N - 1) div 2).

И даже при N = 10^12 сумма второй части получается намного меньше Int64 :)


 
Inovet ©   (2014-12-25 22:29) [16]

> [14] Rouse_ ©   (25.12.14 22:19)
> Ну что-то примерно похожее: 12499999999999999999999999,875

Это для какого N?
калькулятор Windows для 10^12
500 000 000 000 500 000 000 000


 
Inovet ©   (2014-12-25 22:32) [17]

124 999 999 999 750 000 000 000


 
Kerk ©   (2014-12-25 22:33) [18]


> намного меньше Int64 :)

Неправильно посчитал, действительно может вылезти.

Ну в любом случае, просто суммировать - не выход.
Насчет первой части тоже нужно что-то математическое придумать.


 
jack128 ©   (2014-12-25 23:11) [19]


> Ну в любом случае, просто суммировать - не выход.

почему не выход?


 
jack128 ©   (2014-12-25 23:18) [20]


> Насчет первой части тоже нужно что-то математическое придумать.

Насколько я понимаю, начиная с I := N/3+1 до N/2 - 1 остаток будет прогрессией от 1 до N/3 - 1


 
Kerk ©   (2014-12-25 23:18) [21]

Долго потому что. Должна быть возможность решить за разумное время. Это же олимпиадная задачка. Такие задачи изначально не рассчитаны на то, чтоб их по часу считать.


 
Rouse_ ©   (2014-12-25 23:30) [22]

Оптимизированный вариант на максимальном значении работает около 6 секунд, не думаю что это сильно долго :) финальное число пока не называю, бо интрига :)


 
Rouse_ ©   (2014-12-25 23:31) [23]

Кстати жек, тыж алго решения знаешь, проверь у себя, у тебя такое же время выходит?


 
Sha ©   (2014-12-26 10:24) [24]

> MBo ©   (25.12.14 22:14) [12]
> Сумма второй половины слагаемых Sh находится нетрудно без сложения.

а потом и второй половины от первой половины )


 
MBo ©   (2014-12-26 10:28) [25]

Сделал решение за асимптотическое время Sqrt(N) (т.е. порядка нескольких миллионов операций для 10^12, считается мгновенно).
А вот арифметику для 128 бит, вероятно, реализовал с ошибками, отлаживать пока некогда.
Для N = 1000 получается 176919, что совпадает со значением, вычисленным в рамках простой арифметики.
для N = 1000000000000 получается результат со старшим int64-словом $257A=9594 ~ 176*10^21. Младший и старший байты $E0 и $25


 
Rouse_ ©   (2014-12-26 12:10) [26]

А как делал?
Через рассчет нижнего и верхнего лимитов арифметических прогрессий?


 
MBo ©   (2014-12-26 12:28) [27]

Через сумму квадратов с чередующимися знаками.
Поправка по моим числам (упустил в обном месте возможность переполнения, не уверен, что ещё чего-то не осталось):
старшее слово $2578=9592 ~ 176*10^21. Младший и старший байты $1E и $25


 
MBo ©   (2014-12-26 13:14) [28]

Еще одна итерация ;)
старшее слово $2598=9624 ~ 177*10^21. Младший и старший байты $1E и $25


 
Rouse_ ©   (2014-12-26 13:36) [29]

Во как - я по другому ускорял... как доделаешь - покажи, можно для начала в приват rouse79@yandex.ru


 
Плохиш ©   (2014-12-26 14:19) [30]


> Вроде ничего сложного, однако-ж результат суммирования по
> модулю явно не уложится ни в один из поддерживаемых типов
> (int64 vfrcbvev 8 байт).

Составитель читал книгу Вирта "Описание языка паскаль" и сподобился заменить формулу расчёта факториала своей :-)


 
Rouse_ ©   (2014-12-26 14:27) [31]


> Плохиш ©   (26.12.14 14:19) [30]
> Составитель читал книгу Вирта "Описание языка паскаль" и
> сподобился заменить формулу расчёта факториала своей :-)

Развивай мысль :)


 
MBo ©   (2014-12-26 14:36) [32]

>Rouse_ ©   (26.12.14 13:36) [29]
Отослал


 
Rouse_ ©   (2014-12-26 14:42) [33]

Угу вечером гляну.
Мой вариант данное число вычисляет за
Z := 10000000000000 div 900000;
используя две вершины каждого из пиков.
т.е. цикл крутится порядка полутора секунд, но еще не до конца допилен (это третий вариант решения).
Твой будет четвертым получается :)


 
Rouse_ ©   (2014-12-26 15:47) [34]

Глянул - суматор для 128 бит у тебя верный, один в один можно сказать. А вот остальной подход пока никто из нашего отдела не понял с квадратами, правда у нас корпоратив и сложно сходу в код вникнуть... :)


 
Inovet ©   (2014-12-26 19:35) [35]

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


 
Rouse_ ©   (2014-12-26 20:04) [36]


> Inovet ©   (26.12.14 19:35) [35]

Я отвечал MBo :)
А так-то вообще мой подход (уход от цикла через рассчет арифметической прогрессии, порционно по 900 тысяч элеменов) с посказками доступен тут: http://alexander-bagel.blogspot.ru/2014/12/blog-post.html
Почему именно 900 тысяч? Ну округлил. А так база была - максимальная сумма деления по модулю от 10 в 12-ой которая при суммировании влезет в 8 байт.


 
MBo ©   (2014-12-26 20:41) [37]

Функции 128-битной арифметики, которые мне были нужны (не обрабатываются ситуации, которые исключены в данной задаче):

 TUInt128 = record
   class operator Add(a, b: TUInt128): TUInt128;
   class operator Subtract(a, b: TUInt128): TUInt128;
   class operator Implicit(Value: UInt64): TUInt128;
   class function Sqr128(Value: TUInt128): TUInt128; static;
   case Integer of
     0:        (Lo, Hi: UInt64);
     1:        (LoLo, LoHi, HiLo, HiHi: Cardinal);
     2:        (Bytes: array [0 .. 15] of Byte);
 end;

{ TUInt128 }

class operator TUInt128.Add(a, b: TUInt128): TUInt128;
begin
 if a.Lo > UInt64($FFFFFFFFFFFFFFFF) - b.Lo then
   Inc(a.Hi);
 Result.Lo := a.Lo + b.Lo;
 Result.Hi := a.Hi + b.Hi;
end;

class operator TUInt128.Implicit(Value: UInt64): TUInt128;
begin
 Result.Hi := 0;
 Result.Lo := Value;
end;

class function TUInt128.Sqr128(Value: TUInt128): TUInt128;
var
 ad: TUInt128;
begin
 Result.Hi := UInt64(Value.LoHi) * Value.LoHi;
 Result.Lo := UInt64(Value.LoLo) * Value.LoLo;
 ad.Lo := UInt64(Value.LoHi) * Value.LoLo;
 ad.Hi := ad.LoHi;
 ad.Lo := ad.Lo shl 32;
 Result := Result + ad;
 Result := Result + ad;
end;

class operator TUInt128.Subtract(a, b: TUInt128): TUInt128;
begin
 if a.Lo < b.Lo then
   Dec(a.Hi);
 Result.Lo := a.Lo - b.Lo;
 Result.Hi := a.Hi - b.Hi;
end;



 
MBo ©   (2014-12-26 21:19) [38]

Вот начало ряда сумм S(N) и модули, из которых эти суммы складываются.

N   S     mods  
1   0      0  
2   0      0  0  
3   1      0  1  0
4   1      0  0  1  0
5   4      0  1  2  1  0
6   3      0  0  0  2  1  0
7   8      0  1  1  3  2  1  0
8   8      0  0  2  0  3  2  1  0
9  12      0  1  0  1  4  3  2  1  0
10 13      0  0  1  2  0  4  3  2  1  0
11 22      0  1  2  3  1  5  4  3  2  1  0
12 17      0  0  0  0  2  0  5  4  3  2  1  0
13 28      0  1  1  1  3  1  6  5  4  3  2  1  0

Видно, что правая часть представляет собой арифм. последовательность, сумму которой я уже приводил, а вот левая похитрее. Компактного математического выражения не нашел, но эмпирическое исследование привело к следующему:
главный член суммы резко растет на каждом втором (нечетном) числе, рост квадратичный, и это квадрат выражения (N-1) div 2
Q1 = Sqr((N-1) div 2)
Теперь, если вывести разницу Q1 - S(N), то можно увидеть, что она чаще всего скачет на каждом третьем числе, а сдвиг от начала будет уже 3
Q2 = -Sqr((N-3) div 3)
Далее аналогично
Q3 = Sqr((N-6) div 4)
Q4 = -Sqr((N-10) div 5)
и т.д. Числа сдвига - "треугольные", вида k(k-1)/2, где k - знаменатель.

Итого с использованием длинной арифметики получается функция, которая отличается от такой же для короткой арифметики только типом результата и заменой Sqr на TUint128.Sqr128 (если Multiply реализовать, то в обоих случаях можно одинаково X*X)  

 function SumOfMods128(N: Int64): TUInt128;
 var
   Minus, Divider: UInt64;
   Even: Boolean;
 begin
   Result := 0;
   Minus := 1;
   Divider := 2;
   Even := True;
   while Minus < N do begin
     if Even then
       Result := Result + TUint128.Sqr128((N - Minus) div Divider)
     else
       Result := Result - TUint128.Sqr128((N - Minus) div Divider);
     Minus := Minus + Divider;
     Divider := Divider + 1;
     Even := not Even;
   end;
 end;


 
Плохиш ©   (2014-12-26 22:53) [39]


> Rouse_ ©   (26.12.14 14:27) [31]
>
>
> > Плохиш ©   (26.12.14 14:19) [30]
> > Составитель читал книгу Вирта "Описание языка паскаль"
> и
> > сподобился заменить формулу расчёта факториала своей :
> -)
>
> Развивай мысль :)

Там был приведён код расчёта факториала для любых чисел с выводом результата :-)
Результат хранился в массиве строк или символов, точно не помню. Почти 20 лет прошло.


 
Sha ©   (2014-12-26 23:07) [40]

Если без больших чисел, то как-то так:

function LoSum(m, n: int64): int64;
begin;
 Result:=0;
 while m>0 do begin
   Result:=Result+n mod m;
   m:=m-1;
   end;
 end;

function HiSum(m, n: int64): int64;
var
 i: integer;
 k: int64;
begin;
 Result:=(n-m) * (n-m+1) shr 1;
 i:=1;
 while true do begin
   i:=i+1;
   k:=n div i;
   if k<m then exit;
   k:=(k+m) * (k-m+1) shr 1;
   Result:=Result-k;
   end;
 end;

procedure TForm1.Button1Click(Sender: TObject);
var
 m, n, sum: int64;
begin;
 n:=1000; //sum:=LoSum(n,n);
 m:=trunc(sqrt(n+0.0));
 sum:=LoSum(m,n)+HiSum(m+1,n);
 Memo1.Lines.Add(Format("%d %d",[n, sum]));
 end;



Страницы: 1 2 3 вся ветка

Текущий архив: 2015.09.10;
Скачать: CL | DM;

Наверх




Память: 0.59 MB
Время: 0.073 c
1-1331096953
ssmplec
2012-03-07 09:09
2015.09.10
Проблема с cxGrid RepositoryItem


15-1417707045
Дмитрий С
2014-12-04 18:30
2015.09.10
Получить все открытые документы Excel


1-1331451118
renok
2012-03-11 11:31
2015.09.10
Сериализация PageControl а


2-1392624704
alexdn
2014-02-17 12:11
2015.09.10
Раскрасить Edit


15-1417365650
alexdn
2014-11-30 19:40
2015.09.10
Notepad ++