Показаны сообщения с ярлыком теория чисел. Показать все сообщения
Показаны сообщения с ярлыком теория чисел. Показать все сообщения

вторник, 27 сентября 2011 г.

Теория чисел

Есть несколько задач, которые требуют знания из теории чисел. Например, нахождение простых чисел, разложение на простые множители, деление с остатком, НОД и НОК и т.д. Некоторые из них мы разбирали - это простые числа. Рассмотрим другие.
Разложение числа на простые множители.
Например, число 153 = 3*51=3*3*17=3^2 * 17^1.
Итак, нам нужно определить на каждом шаге первое простое число, которое является делителем данного числа. Из теории чисел: наименьший собственный делитель числа N всегда будет простым. Для поиска первого простого делителя числа N воспользуемся следующей функцией:
function simple(n:integer):integer;
var a:integer;
begin
  a:=2;
  while sqr(a)<=n do begin
    if n mod a = 0 then begin simple:=a; exit; end;
    a:=a+1;
  end;
  simple:=n;
end;


var n,a:integer;
begin
  readln(n);
  while n > 1 do
  begin
     a := simple(n);
     n := n div a;
     writeln(a);
  end;
end.

Иногда требуется вывести не все множители (а они иногда повторяются), а только само число и его степень.

var n,a,k:integer;
begin
  readln(n);
  while n > 1 do
  begin
     a := simple(n);
     k:=0;
    while n mod a = 0 do begin
        n := n div a;
        k:=k+1;
     end;
     writeln(a, '^', k);
  end;
end.
Теперь попробуем найти все простые делители N!=1*2*3*...*N.
Пусть N=10, тогда 10!=1*2*3*4*5*6*7*8*9*10. Видно, что степени 2 2=2^1
4=2^2
6=3*2^1
8=2^3
10=5*2^1
Итого 1+2+1+3+1=8. 
Если разделим 10! на 2^1, то получим 5 вхождений (их можно считать так 10 div 2 = 5). Останется (1-1)+(2-1)+(1-1)+(3-1)+(1-1)=0+1+0+2+0=3 степени двойки. Из них 2 раза входит 2^2 (10 div 4 = 2) и один раз 2^3 (10 div 8 = 1). Так делаем для всех простых чисел:
если p - простое, то k = n div p +n div p^2 + n div p^3 +...
function simple(n:integer):boolean;
var i:integer;
begin
  simple:=true;
  if (n mod 2 = 0) and (n<>2) then simple:=false;
  for i:=2 to round(sqrt(n)) do begin
    if i mod 2 = 0 then continue;
    if n mod i = 0 then begin simple:=false; exit; end;
  end;
end;
                  
var i,n,p,k:integer;
begin
read(n);
for i:=2 to n do
  if simple(i) then begin
  p:=i; k:=0;
  while n div p >0 do begin k:=k+ n div p; p:=p*i; end;
  writeln(i,'^',k);
  end;
end.

Деление с остатком. Если даны два натуральных числа P и Q, то можно поделить P на Q с остатком  R  и частным от деления T. Тогда P=T*Q+R. Остатки от деления на одно и то же число можно складывать и умножать:
(P1+P2) mod Q = (P1 mod Q +P2 mod Q) mod Q
(P1*P2) mod Q = ((P1 mod Q )*(P2 mod Q)) mod Q
Пусть заданы два числа N и M. Необходимо найти остаток от деления числа N! на M. Используя выше приведенное условие получим следующий алгоритм решения задачи:
var n,m,t:integer;
begin
  read(n,m);
  t:=1;
  for I:=1 to n do
    t: = (t*i)mod m;
  write(t);
end.

Перейдем к следующей задаче: нахождение НОД (наибольший общий делитель) и НОК (наименьшее общее кратное). Решается такая задача с помощью алгоритма Евклида для двух натуральных чисел а и b:
пока a<>b
   если a<b то b:=a-b
     иначе если a>b то a:=a-b;
Например, НОД(16, 12)
a=16,  b=12

a<>b? да
a<b? нет
a>b? да
a:=a-b=16-12=4

a<>b? да
a<b? да
b:=b-a=12-4=8

a<>b? да
a<b? да
b:=b-a=8-4=4

a<>b? нет
НОД=4

Вычитание можно заменить взятие остатка. Тогда программа имеет вид:
var a,b,nod:integer;
begin
  read(a,b);
  while a<>b do
    if a<b then b:=b mod a
       else if a>b then a:=a mod b;
  nod:=a;
  write(nod);
end.
Но можно написать программу без условий и оформить в виде функции, при этом нужно учесть, что a>b:
function nod(a,b:integer):integer;
var r:integer;
begin
  if b>a then nod:=nod (b,a) else
  begin
  while b<>0 do
   begin
      r:=a mod b;
      a:=b;
      b:=r;
   end;
   nod:=a;
   end;
end;

var a,b:integer;
begin
  read(a,b);write(nod(a,b));
end.
Для нахождения НОК(a,b) воспользуемся формулой НОК(a,b)=a*b/НОД(a,b).
Если нужно найти НОД (или НОК) нескольких чисел, то последовательно находим
НОД( а1, а2, а3, а4) = НОД(НОД(НОД(а1, а2), а3), а4).

суббота, 19 марта 2011 г.

Простые числа. Решето Эратосфена

Вернемся к простым числам. Есть алгоритм, по которому легко вычислить все простые числа до какого-то заданного числа N - это решето Эрастофена. Суть его в следующем: запишем все числа от 1 до N в ряд. Так как число 1 не простое, то его не рассматриваем, т.е. вычеркиваем (число, которое вычеркиваем - подчеркнуто, а простое - выделено полужирным).
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,...,N
1. Берем следующее невычеркнутое число 2.
2. Проверяем все числа от 3 до N на делимость на 2. Если число делится на 2 без остатка, то вычеркиваем его.
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,...,N
4. Переходим к следующему невычеркнутому числу - это 3, и повторяем п.2, но уже числа проверяем с 4 до N и проверяем делимость на 3.
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,...,N
5. Переходим к следующему невычеркнутому числу - это 5, и повторяем п.2, но уже числа проверяем с 6 до N и проверяем делимость на 5.
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,...,N
и т.д.
Видно, что алгоритм можно оформить в виде циклов. Для проверки делимости можно использовать операцию mod - взятие остатка.
Вопрос: как оформить процесс вычеркивания в Паскале?
Есть несколько способов: с помощью структурированных типов множество, массив или строки.

C помощью множества
Множество целых чисел не превышает значения 255. Если число не простое - исключаем его из множества. Перед работой присваиваем множеству диапазон чисел от 2 до N. :
Var M:set of byte;
    i,k,N:byte;
begin
{ввод и инициализация}
    readln(N);
    M:=[2..N];
    k:=2;
{алгоритм решето Эратосфена}
    repeat
    for i:=k+1 to N do
       if (i in M) and(i mod k=0) then M:=M-[i];
    inc(k);
    while (k<=N) and not (k in M) do inc(k);
    until k>N;
{вывод ряда простых чисел}
    for i:=2 to N do if i in M then write(' ',i);
end.

C помощью массива
Лучше использовать массив с элементами логического типа. Будем считать, что на i-ом месте массива P стоит true (или 1) если число простое, иначе false (или 0). Перед работой массив нужно  "проинициализировать" значениями true (или 1), т.е. считаем, что все числа простые (как при доказательстве от противного):
Var P:array [2..65535] of boolean;
    i,k,N:word;
begin
{ввод и инициализация}
    readln(N);
    fillchar(P,N,1);
    k:=2;
{алгоритм решето Эратосфена}
    repeat
    for i:=k+1 to N do
       if P[i] and(i mod k=0) then P[i]:=false;
    inc(k);
    while (k<=N) and (not P[k]) do inc(k);
    until k>N;
{вывод ряда простых чисел}
    for i:=2 to N do if P[i] then write(' ',i);
end.

C помощью строк
Будем считать, что на i-ом месте строки S стоит '*' если число простое, иначе ' ' (пробел). Перед работой со строкой нужно ее "инициализировать" символами '*', т.е. считаем, что все числа простые (как при доказательстве от противного). Кроме этого нужно знать, что строка содержит не более 255 символов (как сделать еще больше? Создать массив символов - это другой способ):
Var S:string[255];
    i,k,N:byte;
begin
{ввод и инициализация}
    readln(N);
    fillchar(S,N,'*');
    S[1]:=' ';k:=2;
{алгоритм решето Эратосфена}
    repeat
    for i:=k+1 to N do
       if (S[i]='*')and(i mod k=0) then S[i]:=' ';
    inc(k);
    while (k<=N) and(S[k]=' ') do inc(k);
    until k>N;
{вывод ряда простых чисел}
    for i:=2 to N do if S[i]='*' then write(' ',i);
end.

Я специально привела три способа, чтобы показать какие есть возможности у Паскаля для реализации этого алгоритма. каждый алгоритм имеет свои ограничения на конечное число: 255 и 65535. Можно попробовать написать программу с использованием указателей на ячейку памяти. Тогда ограничение для чисел здесь увеличивается до типа longint или comp, а если написать операцию проверки делимости для вещественных чисел, то и для extended (самого большого числа). Но для вещественных чисел нужно менять цикл for на While, что может привести к увеличению времени работы алгоритма.

Пробуйте.

PS: Кроме этого разбора я показала один из приемов в обучении: чтобы запомнить алгоритм - нужно его попробовать написать другим способом и если через некоторое время переменные, операторы, типы будут схожи, то вы выработали свой стиль программирования и запомнили алгоритм. Удачи.

воскресенье, 19 декабря 2010 г.

Простые числа

Есть несколько способов найти все простые числа или определить, что число простое. Рассмотрим первый который приходит обычно начинающим программистам (будем рассматривать только основной код):
readln(n);
if n=1 then write('число не простое')
else begin  
simply:=true; //предположим, что n - простое, проверим обратное 
for i:=2 to n-1 do
if n mod i = 0 then begin
write('число не простое');
simply:=false;
break;//выход из цикла
end;
if simply then write('число простое');
end;
Второй способ более рационален и чаще всего используется. Это просто немного модифицированный первый способ, только проверяем делимость до корня квадратного из n (это предположение доказывается в курсе алгебры вуза, здесь мы его только применяем):
readln(n);
if n=1 then write('число не простое')
else begin  
simply:=true;  

for i:=2 to round(sqrt(n)) do
if n mod i = 0 then begin
write('число не простое');
simply:=false;

break;

end;
if simply then write('число простое');
end;
Есть другой способ этого же метода, только используется цикл "пока", где проверяется только нечетные делители. Перед этим проверяется число на четность и обязательно исключить 2.
readln(n);
if n=1 then write('число не простое')
else if n=2 then write('число простое')
else if n mod 2 = 0 then write('число не простое')
else begin 
simply:=true;

i:=3;
k:=round(sqrt(n));
while i<=k do
begin
if n mod i = 0 then 
begin
write('число не простое');
simply:=false;

break;

end;
inc(i,2);
end;
if simply then write('число простое');
end;
Если число n достаточно большое (типа longint например), то лучше использовать второй пример. Цикл for работает как счетчик и не требует дополнительных проверок как в цикле while, поэтому будет работать быстрее.
Еще один алгоритм нахождения всех простых чисел - это решето Эратосфена. Пусть все числа являются индексами массива 
a:array[1..1000]of boolean;
Предположим, что все числа простые. Для задания наальных значений используем функцию
fillchar(a,sizeof(a),true); 

Проверяем каждый раз на делимость новое найденное простое число:
a[1]:=false;a[2]:=true;
for i:=2 to n-1 do
if a[i] then begin
 k:=i;
 for j:=i+1 to n do
  //исключаем из списка простых чисел, то число, которое делится на текущее простое число 
 if a[j] and (j mod k=0) then a[j]:=false; 
 end;
//выводим все простые числа
for i:=1 to n do
if a[i] then write(i,' ');
Этот алгоритм действует только до чисел типа integer, так как массив принимает индексы только этого типа. В новых версиях компиляторов языка Паскаль: Free Рascal, Delphi, Lazarus - можно использовать динамический массив:
var a:array of boolean;
В программе после ввода числа n используем процедуру задания размера
setlength(a,n+1);
и задания начальных данных
for i:=1 to n do a[i]:=true;
В днамическом массиве нумерация начинается с 0. 

Только для больших чисел все равно будет считать долго. Лучше применить третий или второй вариант, изменив вывод: вместо сообщения простое или нет число, выводить только простые числа:
readln(n);
if n=1 then exit; //конец процедуры или программы 

for i:=2 to n do
if i=2 then write(i,' ')
else if i mod 2 = 0 then continue
else begin
simply:=true;
k:=round(sqrt(i));

j:=3;
while j<=k do
begin
if i mod j = 0 then
begin
 simply:=false;
 break;
end;
inc(j,2);
end;
if simply then write(i,' ');
end;


Если нужно запомнить все простые числа в массив, используем следующий вариант:
readln(n);j:=0;
if n=1 then exit;

for i:=2 to n do begin
 if i=2 then begin inc(j);a[j]:=i end //добавим первое простое число в массив

 else if i mod 2 = 0 then continue;//исключаем четные числа
simply:=true;
for k:=1 to j do //проверяем на делимость простых чисел
if i mod a[k] = 0 then
begin
simply:=false;
break;
end;
 
//добавим в массив следующее простое число
if simply then begin inc(j);a[j]:=i end;
end;

 for i:=1 to j do write(a[i],' ');
Массив нужно объявлять либо статическим, либо динамическим (см. примеры выше).Если массив большой и типа longint, то можно размер динамического массива менять по мере добавления простых чисел в массив:
var n,i,k,j:longint;
    simply:boolean;
    a:array of longint;//массив ОБЯЗАТЕЛЬНО нужно прописать последним, чтобы не было перекрытия области уже объявленных переменных
begin
readln(n);j:=0;setlength(a,1);
if n=1 then exit;
for i:=2 to n do begin
 if i=2 then begin inc(j);a[j-1]:=i end //добавим первое простое число в массив
 else if i mod 2 = 0 then continue;//исключаем четные числа
simply:=true;
for k:=0 to j-1 do //проверяем на делимость простых чисел
if i mod a[k] = 0 then
begin
simply:=false;
break;
end;
//добавим в массив следующее простое число
if simply then begin inc(j);setlength(a,j);a[j-1]:=i; end;
end;
for i:=0 to j-1 do write(a[i],' ');
 writeln(j);  
end.
Ну вот вроде и все.