Пришлось тут на днях решать для студента (первого курса) задачку.
Дословно выглядит вот так:
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 максимум 8 байт).
Сразу уточню: задачка олимпиадная, причем свежего 14-го года :)
Конечно странно что такие вещи задают первокурснику, но...
В итоге для обкатки накидал такое решение на двух полусумматорах - банальная битовая арифметика:
#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;
}
Либо, чтобы было проще понять, вариант на дельфи, но (увы) без коментариев:
program Project1;
 
{$APPTYPE CONSOLE}
 
{$R *.res}
 
uses
  Windows,
  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);
   if SFlag then
      HalfResult := HalfResult or (Int64(1) 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);
   if SFlag then
      HalfResult := HalfResult or (Int64(1) 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.
Сразу скажу - ЭТО работает, причем работает правильно (можно даже использовать для проверки), однако есть более грамотный и более быстрый вариант решения в 17 строчек кода, вместо процедур Add и FullAdder (на дельфи или на си - кому как удобнее) :)
Задача - попробовать найти данный вариант решения или предложить еще более оптимальный вариант :)
С наступающим :)
UPD: дам подсказку, график прохода по модулю выглядит вот так:
Определение верхнего лимита прогрессии:
X := Limit div (Z + 1) - (Z - (Limit mod (Z + 1)));
Где:
X - предел положительной вершины
Limit - значение числа N из оригинальной задачи
Z - текущее прирастание прогрессии (т.е. при Z = 1, прогрессия 0, 1, 2... при Z = 5, прогрессия 0, 5, 10... )
т.е. грубо идем от 0 до 1000 - пики выглядят:
499, 332, 247, 196, 165 и т.д....

 
Мы кембриджев не кончали, в верхней математике не сильно шарим - мы по простому
ОтветитьУдалитьtype
Int128 = packed record
case integer of
0: (i128: array [0..15] of byte);
1: (s0,s1: int64);
end;
var
R: int128;
i,N: int64;
Over: boolean;
begin
N:=Round(power(10,12));
R.s1:=0;
R.s0:=0;
i:=1;
Over:=false;
while i <= N do begin
R.s0:=R.s0+(N mod i);
Inc(i);
if (R.s0 < 0) and (not Over) then Over:=true;
else if (R.s0 >= 0) and Over then begin
Over:=false;
Inc(R.s1);
end;
end;
end;
Веселого нового года :)
А я все думаю - зачем люди строки нумеруют?
УдалитьВот и мне подарок к новому году , теперь знаю - чтобы глупая машина структуру кода не съедала.
1 type
2 Int128 = packed record
3 case integer of
4 0: (i128: array [0..15] of byte);
5 1: (s0,s1: int64);
6 end;
7
8 var
9 R: int128;
10 i,N: int64;
11 Over: boolean;
12
13 begin
14 i:=$7fffffffffffffff;
15 N:=Round(power(10,12));
16 R.s1:=0;
17 R.s0:=0;
18 i:=1;
19 Over:=false;
20 while i <= N do begin
21 R.s0:=R.s0+N mod i;
22 Inc(i);
23 if (R.s0 < 0) and (not Over) then begin
24 Over:=true;
25 end
26 else if (R.s0 >= 0) and Over then begin
27 Over:=false;
28 Inc(R.s1);
29 end;
30 end;
31 end;
А она все равно съела...
ОтветитьУдалитьВ комментариях подсветка кода и разметка не поддерживается :)
УдалитьПо самому коду, в принципе да. как-то так и надо, только ждать долго, около трех дней, поэтому нужно что-то более эффективное :)
Модуль и сложение никуда не денешь.
ОтветитьУдалитьБолее эффективное - только на ассемблере
Конечно денешь, в этом то собственно и есть суть задачи. Более того скажу, что самый быстрый из известных мне вариантов решения решает данную задачу для N = 10^12 за 73 миллисекунды :)
Удалить1 января решение будет опубликовано.
суть олимпиадных задач и состоит в знаниях и применениях алгоритмов для быстрого решения, а не решения в лоб. Так что естественно решаться задача будет не по данной в условии формуле прямым счетом.
УдалитьТак где же решение?
ОтветитьУдалитьОпубликовал
УдалитьYou are going to calculate N%1+ N%2+ N%3+...+ N%N - а это разве не арифметическая прогрессия?
ОтветитьУдалитьНе совсем, грубо говоря это набор арифметических прогрессий.
УдалитьСкажите, где можно найти вторую часть ответа на задачку номер 2?
ОтветитьУдалитьА я ее не публиковал, но там в кратце трюк заключается в восстановлении pwndOrg и снятия флага DCX_INUSE у структуры tagDCE при вызове GetDCEx с флагом DCX_INTERSECT
УдалитьА нужно сделать, чтобы было всем понятно, а то не совсем понятно. :)
УдалитьСтатью писать лень, вот тут я основные позиции вкратце накидал, сишный код взят из исходников w2k.
Удалитьhttp://rouse.drkb.ru/tmp/canvas.zip
Думаю не только мне интересно.
УдалитьДа и как-то странно выглядит, что нет 2-й части на ответ. Не хорошо это.
А заметочку можно за неделю осилить для блога, чтобы он жил.
Посмотрел код, честно говоря не понял откуда в такой простой задачке появились структуры и т.д. :)
Структуры - это скажем там внутренности GDI. А статьи пока что не будет, дело в том что когда я готовил вторую часть ответа и перепроверял себя наткнулся на один не понятный мне самому момент, без изучения которого ответ был бы не полным. А на его изучение требуется время, которого и так мало, поэтому решил все это отложить в сторонку. Как созрею - так сразу опубликую все целиком :)
Удалить