Thứ Tư, 19 tháng 2, 2014

Chữ số cuối khác 0



Chữ số cuối khác 0
Đề thi Tin học Quốc gia Ireland, 1994.
Tìm chữ số cuối cùng khác 0 của n! với n trong khoảng 1..100.
Thí dụ:
-        n = 7, kết quả = 4.
-        n = 15, kết quả = 8.
Bài giải
Thuật giải của bạn Việt Hưng (Hà Tây, 2002) cho phép mở rộng giới hạn của n đến hàng chục vạn và nếu bạn muốn, có thể tiếp tục mở rộng đến hàng triệu.
Ý tưởng chính của Việt Hưng nằm ở công thức: 2 ´ 5 = 10 (hai lần năm là mười). Thật vậy, ta biết:
n! = 1.2.3...n
Các chữ số cuối cùng bằng 0 của n giai thừa được sinh ra khi và chỉ khi trong khai triển ra thừa số nguyên tố của tích trên có chứa các cặp thừa số 2 và 5. Vậy thì trước hết ta đếm số lượng các thừa số 2, kí hiệu là d2 và số lượng các thừa số 5, kí hiệu là d5.
Thí dụ, với n = 15 ta có dạng khai triển ra thừa số nguyên tố của n giai thừa như sau:
n! = 1.2.3.(2.2).5.(2.3).7.(2.2.2).9.(2.5).11. (2.2.3).13.(2.7).(3.5)
Do đó d2 = 11 và d5 = 3. Vậy ta có ba cặp 2.5 = 10 và số mũ dôi ra của thừa số 2 so với thừa số 5 sẽ là d2 – d5 = 11 – 3 = 8. Khi đó, kết quả sẽ là:
chữ số cuối cùng khác 0 của 15! = chữ số cuối cùng của k.2d2-d5
Trong đó k là tích của các thừa số còn lại.
Dễ thấy với mọi n, ta luôn có d2 ³ d5 vì cứ hai số liên tiếp thì có một số chẵn (chia hết cho 2), còn năm số liên tiếp mới có một số chia hết cho 5.
Việc còn lại là lấy tích k của các số còn lại. Vì tích này không bao giờ tận cùng bằng 0 cho nên ta chỉ cần giữ lại một chữ số cuối cùng bằng cách lấy mod 10.
Để tính chữ số tận cùng của 2m với m = d2 – d5 > 0 ta để ý đến tính tuần hoàn của nó, cụ thể là ta chỉ cần tính chữ số tận cùng của 2(m mod 4) với các trường hợp:
m mod 4 = 0, 1, 2 và 3.
Theo thí dụ trên ta có m mod 4 = 8 mod 4 = 0, do đó chữ số cuối của 2m là 6 chứ không phải là 1 vì m > 0. Ta tính được (những cặp 2 và 5 được gạch dưới)
15! = 1.2.3.4.5.6.7.8.9.10.11.12.13.14.15 =
         = 1.2.3.(2.2).5.(2.3).7.(2.2.2).9.(2.5).11. (2.2.3).13.(2.7).(3.5)
         Þ (3.3.7.9.11.3.13.7.3). 28mod 10 =
                        = ((k mod 10) . (28 mod 10)) mod 10 = (3.6) mod 10 = 8.
Chữ số cuối cùng khác 0 của 15! là 8.
Lưu ý rằng (a.b) mod m=((a mod m).(b mod m)) mod m cho nên ta có thể lấy mod ở các bước trung gian với số lần tùy ý.
Để tránh việc tràn số khi sử dụng các biến dung lượng nhỏ như kiểu word thay vì dùng longint chúng ta có thể tăng thêm một phép toán mod nữa. Chẳng hạn, khi tích luỹ kết quả, thay vì viết
k := (k*c) mod 10;
ta nên viết
k := (k*(c mod 10)) mod 10;
trong đó k là số có một chữ số, c là số có thể rất lớn, đủ để làm tích (k*c) vượt quá giới hạn cho phép. Thí dụ, nếu khai báo kiểu dữ liệu là word thì khi k = 8, c = 17999 ta có k*c = 8*17999 = 143992 > 65535 (giới hạn của word), trong khi 8 và 17999 đều nằm trong giới hạn cho phép.
Bình luận
Để ý rằng:
14! = 87178291200, có chữ số cuối cùng khác 0 là 2
15! = 1307674368000, có chữ số cuối cùng khác 0 là 8.
Nếu để tính 15! mà bạn chỉ lấy một chữ số cuối khác 0 của các phép tính trung gian thì sau khi tính chữ số cuối của 14! bạn sẽ nhận được 2 và cuối cùng là:
(2*15) mod 10 = 30 mod 10 = 3.
Kết quả này là sai vì chữ số cuối khác 0 của 15! là 8.
Chương trình sau đây chứa thủ tục find tìm chữ số cuối cùng khác 0 của n! với n trong khoảng 1..65535.
Ta thực hiện một cải tiến nhỏ như sau. Thay vì đếm số lượng các thừa số 2 (d2) và thừa số 5 (d5) sau đó làm phép trừ d2 – d5, ta đếm luôn một lần hiệu số này và ghi vào biến m. Cụ thể là với mỗi giá trị i = 2..n ta đếm số lượng các thừa số 2 trước, sau đó trừ đi số lượng các thừa số 5.
(*  Pascal  *)
(*---------------------------------------
 Tim chu so khac khong cuoi cung cua n!
----------------------------------------*)
uses crt;
function find(n: longint): longint;
var m: longint;
  {m – hieu so cac thua so 2 và thua so 5}
  i,k,c: longint;
begin {k – ket qua trung gian}
k :=  1; m := 0;        
find := k;
if (n <= 1) then exit;
d := 0;
for i := 2 to n do
         begin
c := i;
while c mod 2 = 0 do
            begin
m :=  m+1; c :=  c div 2;
            end;
while c mod 5 = 0 do
            begin
m :=  m-1; c :=  c div 5;
            end;
k := (k*(c mod 10)) mod 10;
          end;
case (m mod 4) of
0: c :=  6;
1: c :=  2;
2: c :=  4;
3: c :=  8;
end;
find := (k*c) mod 10;
end;
procedure run;
 var n: longint;
begin
writeln('--------------------');
repeat
write(' Nap N (Muon dung chuong trinh, bam 0 ): ');
read(n);
if n = 0 then halt;
writeln(' find(',n,') = ',find(n));
until false;
end;
BEGIN
run;
END.
Kĩ thuật gán trước
Bạn có thể thay lệnh Case trong việc tính chữ số tận cùng của 2m bằng các thao tác sau:
1. Khai báo và gán trước trị cho mảng LuyThua
const LuyThua: array[0..3] of word = (6,2,4,8);
Ý nghĩa của dòng lệnh trên: khai báo một mảng kiểu word với bốn phần tử có chỉ số biến thiên từ 0..3 và gán trước trị cho mảng này là:
LuyThua[0] :=  = 6; {= 24 mod 10}
LuyThua[1] = 2; {= 21 mod 10}
LuyThua[2] = 4; {= 22 mod 10}
LuyThua[3] = 8; {= 23 mod 10}
Chú ý rằng, do đòi hỏi phải khai báo trước và gán đồng thời nên Turbo Pascal 7.0 quy định đặt khai báo này sau từ khoá const. Tuy nhiên LuyThua vẫn là biến mảng chứ không thể là hằng.
2. Khi cần tìm chữ số cuối cùng c của 2m bạn khỏi phải dùng lệnh case, chỉ cần viết:
c  :=  LuyThua[m mod 4];
Hàm Find theo phương án mới sẽ như sau:
const LuyThua: array[0..3] of word = (6,2,4,8);
{-------------------------------------------}
Find – Tìm chữ số cuối cùng khác 0 của n!
Phuong an dung mang LuyThua gan truoc
--------------------------------------------}
function find(n: longint): longint;
var m: longint;
{m – hieu so cac thua so 2 và thua so 5}
i,k,c: longint; {k – ket qua trung gian}
begin
k :=  1; m := 0;
find := k;
if (n <= 1) then exit;
d := 0;
for i := 2 to n do
         begin
c := i;
while c mod 2 = 0 do
            begin
m  :=  m+1; c  :=  c div 2;
            end;
while c mod 5 = 0 do
            begin
m  :=  m-1; c  :=  c div 5;
            end;
k  := (k*(c mod 10)) mod 10;
          end;
 find  := (k*LuyThua[m mod 4]) mod 10;
end;

Không có nhận xét nào:

Đăng nhận xét