Tuy nhiên khi thực hiện các phéptính với số nguyên ngoài phạm vi biểu diễn được cung cấp có nhiều hơn 20 chữsố ta cần thiết kế cách biểu diễn và các hàm thực hiện các phép toán cơ bản vớ
Trang 1MỤC LỤC
A MỞ ĐẦU ………
I Lí do chọn đề tài ………
II mục đích nghiên cứu ……… ………
III Đối tượng nghiên cứu ……….………
IV Phương pháp nghiên cứu ………
2 2 2 2 2 B NỘI DUNG I Cơ sở lí luận ……… 3
II Thực trạng vấn đề trước khi áp dụng ……… 3
III Giải pháp sử dụng ……… 3
Phần một: XÂY DỰNG CÁC PHÉP TOÁN SỐ HỌC TRÊN SỐ LỚN … 3
1.1. Biểu diễn số nguyên lớn……… 3
1.2. Xây dựng các phép toán số học ……… 5
1.2.1 Phép so sánh ……… 5
1.2.2 Phép cộng một số lớn với một số nhỏ ……… 5
1.2.3 Cộng hai số nguyên lớn ……… 6
1.2.4 Phép trừ ……… 6
1.2.5 Phép nhân một số lớn với một số nhỏ ……… 7
1.2.6 Phép nhân hai số nguyên lớn ……… 7
1.2.7 Phép chia một số nguyên lớn cho một số nguyên nhỏ (lấy phần thương nguyên (div) và dư (mod)) ………
8 1.2.8 Phép chia hai số nguyên lớn (lấy phần thương nguyên (div) và dư (mod)) ………
8 Phần hai: MỘT SỐ BÀI TOÁN ỨNG DỤNG XỬ LÝ SỐ LỚN ……… 9
2.1 Bài toán 1 Tính giai thừa ……….……… 9
2.2 Bài toán 2 (Đề Bảng C Tin học trẻ không chuyên toàn quốc) ….……… 10
2.3 Bài toán 3 (Đề thi OLIMPIC Tin học sinh viên Việt Nam 2005) ….… 12
2.4 Bài toán 4 (Đề thi OLP 2004) ……… 15
Phần ba: bài tập luyện tập ……… … 18
IV Hiệu quả của sáng kiến ……… 20
C KẾT LUẬN, KIẾN NGHỊ ……… 21
I Kết luận ……… 21
II Kiến nghị ……… 21
Trang 2A MỞ ĐẦU
I Lí do chọn đề tài.
Trong mỗi ngôn ngữ lập trình thường có một số kiểu dữ liệu chuẩn chobiết phạm vi giá trị có thể lưu trữ, dung lượng bộ nhớ cần thiết để lưu trữ và xácđịnh các phép toán có thể tác động lên dữ liệu Chẳng hạn trong Turbo Pascal,một số kiểu dữ liệu dạng số nguyên bao gồm: byte, integer, word, longint, int64,qword Trong đó kiểu qword có phạm vi lớn nhất, mỗi giá trị lưu trữ trong 8byte cho phép biến lưu số tối đa có 20 chữ số Tuy nhiên khi thực hiện các phéptính với số nguyên ngoài phạm vi biểu diễn được cung cấp (có nhiều hơn 20 chữsố) ta cần thiết kế cách biểu diễn và các hàm thực hiện các phép toán cơ bản vớicác số nguyên lớn
Gần đây trong các kỳ thi học sinh giỏi người ta thường hay đưa ra các bàitoán cần kết hợp cả xử lý số lớn Vì vậy tìm hiểu về biểu diễn số lớn và các phéptoán với số lớn là cần thiết Trong khuôn khổ của đề tài này, tôi đưa ra cách biểudiễn số nguyên không âm lớn và các phép toán trên đó
Cụ thể Tên đề tài: “KỸ NĂNG DÙNG MẢNG MỘT CHIỀU ĐỂ XỬ LÝ SỐ
NGUYÊN LỚN GIÚP GIẢI CÁC BÀI TOÁN KHÓ TRONG LẬP TRÌNH PASCAL”
II Mục đích của đề tài
Sử dụng các ví dụ cụ thể về các số nguyên lớn để học sinh nắm được phương
pháp chuyển đổi Và thông qua các ví dụ đó hướng dẫn học sinh làm một số bàitoán ứng dụng sử lí số nguyên lớn
III Đối tượng nghiên cứu.
Học sinh giỏi khối 11 tại trường THPT NGỌC LẶC
Sử dụng máy tính để chạy các chương trình
IV Phương pháp nghiên cứu
- Kết hợp thực tiễn việc ôn luyện học sinh giỏi ở trường THPT THPT NGỌCLẶC
- Có tham khảo các tài liệu về ngôn ngữ lập trình Pascal và tài liệu về sáng kiếnkinh nghiệm
Trang 3B NỘI DUNG I.Cơ sở lí luận
Khi giải quyết các bài toán về số trong pascal ta dùng các kiểu dữ liệu sau:byte, integer, word, longint, int64, qword trong đó kiểu qword có phạm vi lớnnhất cho phép biến lưu số tối đa có 20 chữ số Khi số nguyên quá lớn vượt quagiới hạn này (ví dụ số nguyên 21 số trở lên) thì việc lưu trữ nó bằng các kiểu dữliệu trên là điều không thể
II Thực trạng vấn đề trước khi áp dụng
1 Thuận lợi:
Được sự quan tâm của Sở Giáo dục & Đào tạo và Ban giám hiệu nhà trường,
bộ môn Tin học được trang bị phòng máy tính cùng với các máy chiếu phục vụcho nhu cầu công tác giảng dạy ứng dụng CNTT của giáo viên
III.Giải pháp sử dụng
Phần một: XÂY DỰNG CÁC PHÉP TOÁN SỐ HỌC TRÊN SỐ LỚN
1.1 Biểu diễn số nguyên lớn
Thông thường người ta sử dụng các cách biểu diễn số nguyên lớn sau:
Xâu kí tự: Mỗi kí tự của xâu tương ứng với một chữ số của số nguyên lớn tính từ
trái qua phải
Mảng các số: Sử dụng mảng lưu các chữ số (hoặc một nhóm chữ số) của số
Trang 4Để dễ dàng thực hiện các phép toán ta lưu mảng theo thứ tự ngược lại với số,tức là các chữ số trong mảng lưu theo chiều từ trái sang phải: chữ số hàng đơn vị
là phần tử thứ nhất, chữ số hàng chục là phần tử thứ hai, …
Ví dụ: số X = 1965 thì mảng X sẽ là: (5,6,9,1,0,0,0…) tức là X[1] = 5; X[2] = 6;X[3] = 9; X[4] = 1; X[5] = 0,…
Dữ liệu vào: Gồm hai số nguyên lớn hoặc số nguyên lớn và số nguyên nhỏ (số
nhỏ là số có kiểu chuẩn: byte, integer, word, longint…), hai số cách nhau mộtdòng trống, mỗi số nguyên lớn có thể ghi trên nhiều dòng
Thủ thục đọc file dữ liệu vào gồm hai số nguyên lớn:
repeat readln(f,s); ma:=ma+length(s); until s='';
repeat readln(f,s); mb:=mb+length(s); until s='';
ma:=0; fillchar(a,sizeof(a),0); {a là số nguyên lớn}
repeat readln(f,s); ma:=ma+length(s); until s='';
close(f); reset(f); n:=ma;
Trang 5Để so sánh hai số nguyên lớn a, b ta so sánh độ dài của hai số Nếu hai số có
độ dài bằng nhau thì so sánh từng chữ số của hai số Hàm cmp trả về giá trị 1nếu a lớn hơn b, trả về giá trị -1 nếu a nhỏ hơn b và trả về giá trị 0 nếu a bằng b
Function cmp(a,b:bigint):integer;
Var i:integer;
Begin
If ma>mb then begin cmp:=1; exit; end
Else if ma<mb then begin cmp:=-1; exit; end
Else
For i:= ma downto 1 do
If a[i]>b[i] then begin cmp:=1; exit; end
Else if a[i]<b[i] then begin cmp:=-1; exit; end;
- nho:=t div 10;
- Tong[i]:=t mod 10;
Lưu ý: nho có thể lớn hơn 10.
Procedure cong( a:bigint; num:longint; var
ma:=ma+1;
tong[ma]:=nho mod 10; nho:=nho div 10;
Trang 6Chú ý: Trong Turbo Pascal kết quả trả lại của hàm là các kiểu cơ sở như: số
nguyên, số thực, kí tự, và kiểu xâu Nếu muốn trả lại kết quả là kiểu cấu trúcnhư mảng thì chỉ có cách dùng tham biến Nhưng Free Pascal cho phép kết quảcủa hàm có thể là kiểu cấu trúc Như vậy nếu viết trong Free Pascal ta có thểthay thủ tục cộng bằng hàm cộng hai số
if ma >=mb then begin max:=ma; for
i:=mb+1 to ma do b[i]:=0; end
else begin max:=mb; for i:=ma+1 to
mb do a[i]:=0; end;
t:=0; fillchar(tong,sizeof(tong),0);
for i:=1 to max do
begin t:=t+a[i]+b[i];
- Trường hợp a>b thì thực hiện a trừ cho b
- Trường hợp a<b thì lấy b trừ cho a sau đó thêm dấu âm đằng trước
Procedure tru (a,b:bigint; var
hieu:bigint);
var i,nho:longint;
begin
if ma >=mb then begin max:=ma;
for i:=mb+1 to ma do b[i]:=0; end
else begin max:=mb; for i:=ma+1
to mb do a[i]:=0; end;
nho:=0;
begin nho:=a[i]-b[i]-nho;
if nho<0 then begin hieu[i]:=10+nho; nho:=1; end
else begin hieu[i]:=nho; nho:=0; end;
end;
{xoa so 0 o dau}
while (max>1) and (kq[max]=0)
Trang 7for i:=1 to max do do max:=max-1;
end;
1.2.5 Phép nhân một số lớn với một số nhỏ
Thuật toán: Nhân một số lớn x với số nhỏ num Ta không cần quan tâm num có
bao nhiêu chữ số cứ coi nó chỉ là số có một chữ số và nhân với từng chữ số của
số lớn x từ phải sang trái, nhớ có thể lớn
procedure nhan (x:bigint; num:longint;
1.2.6 Phép nhân hai số nguyên lớn
Thuật toán: Như phép nhân thông thường đã học và thực hiện trên giấy
Ví dụ:
procedure
procedure nhan2so(a,b:bigint; var tich:bigint); {tích hai số lớn}
var x:bigint; j,k,l:integer;
Theo trên ta thấy việc nhân hai số gồm hai thao tác:
Thao tác 1: Thực hiện việc nhân mỗi chữ số (từ phải sang
trái cho đến hết) của số thứ hai với số thứ nhất
Thao tác 2: Thực hiện phép cộng tất cả các số vừa tính
Trang 8end;
end;
1.2.7 Phép chia một số nguyên lớn cho một số nguyên nhỏ (lấy phần thương nguyên (div) và dư (mod))
Thuật toán chia một số lớn cho một số nhỏ thao tác từ trái qua phải, mỗi lần
hạ một phần tử xuống, gộp vào biến nhớ, thực hiện phép chia trực tiếp (vì sốchia là một số nhỏ)
Procedure chia( a:bigint;
if nho<num then begin i:=i+1;
thuong[i]:=nho div num; end;
{Xóa số 0 ở đầu của số thuong}
while (thuong[1]=0) and (max>1) do begin
for j:=1 to max-1 do thuong[j]:=thuong[j+1];
liên tiếp từng đoạn của số bị chia (từ trái sang phải) cho số chia:
Cắt lần lượt từng đoạn của số bị chia tính từ bên trái (có cộng thêmphần dư của các bước trung gian)
Đem chia các đoạn đó cho số chia bằng phép toán trừ
Thương tìm được là dãy các số Dãy số này là kết quả của phép chiacác đoạn cho số chia (được phát triển dần về phía bên phải)
Phần dư của phép chia chính là đoạn còn lại không thể chia được nữa
procedure chia(a,b:bigint;var thuong,du:bigint);
Trang 9m:=i; {m là độ dài của thuong}
{Xóa số 0 ở đầu của số thuong}
while (thuong [1]=0) and (m>1) do
2.1 Bài toán 1 Tính giai thừa
Hãy tính N! (N<=2000)
Thuật toán:N! = 1*2*…*N Vì N rất lớn, như vậy khi nhân các giá trị có thể rất
lớn, ta không thể dùng các kiểu số có sẵn để lưu trữ mà phải dùng xâu hoặcmảng để lưu
Để tính giá trị của biểu thức ta phải thực hiện thao tác: nhân một số lớn với số
inc(max);
tich[max]:=nho mod 10;
Trang 10bigint=array[1 maxn] of chuso;
var kq,tich:bigint; max,n,i:integer;
procedure nhan(x:bigint; num:longint;
2.2 Bài toán 2 (Đề Bảng C Tin học trẻ không chuyên toàn quốc năm 2004)
Xét dãy số nguyên gồm n số hạng a1, a2, …, an (n được gọi là độ dài của dãy)được xác định như sau:
a1 = a2 = a3 =1; an = an-3 +an-1 với n>3
Yêu cầu: Hãy xác định độ dài lớn nhất của dãy thỏa mãn mọi giá trị số hạng đềunhỏ hơn hay bằng một giá trị nguyên k cho trước
Dữ liệu vào: từ file văn bản DAYSO.INP gồm một dòng chứa không quá 255
chữ số viết liền nhau (không có các số 0 vô nghĩa ở đầu) biểu diễn giá trị k
Kết quả: Ghi ra file văn bản DAYSO.OUT độ dài lớn nhất của dãy tìm được.
Trang 11assign(f,fi); reset(f); assign(g,go); rewrite(g);
end;
{================================================} procedure Closef;
if p<l then begin Lonhon:=false; exit; end
else if p>l then begin Lonhon:=true; exit; end
else
for i:=p downto 1 do
Trang 12if k[i]>d[i] then begin lonhon:=false; exit; end
else if k[i]<d[i] then begin lonhon:=true; exit; end;
lonhon:=false;
end;
{===============================================} procedure Main;
2.3 Bài toán 3 (Đề thi OLIMPIC Tin học sinh viên Việt Nam 2005)
Một hệ thống giao thông gồm N nút (các nút được đánh số từ 1 đến N),trong đó bất kỳ hai nút nào cũng có đoạn đường hai chiều nối chúng Ta gọiđường đi giữa hai nút là dãy các đoạn đường kế tiếp nhau, bắt đầu từ một nút vàkết thúc tại nút kia, trong đó không có nút nào trên đường đi được lặp lại
Yêu cầu: Cần đếm tất cả các đường đi khác nhau giữa hai nút bất kỳ của mạnggiao thông đã cho
Ví dụ: Với hệ thống giao thông 4 nút trong hình dưới đây, ta có 5 đường đi nốigiữa hai nút tô đen
Trang 13 Ta dễ thấy rằng: tổng các đường đi
từ 2 đỉnh bất kì là tổng các đường đi có độ dài bằng 1, các đường đi có độdài bằng 2, các đường đi có độ dài bằng 3, …, các đường đi có độ dàibằng n-1 Cũng chính là tổng các đường đi không đi qua đỉnh trung giannào, đi qua 1 đỉnh trung gian, qua 2 đỉnh trung gian, …, qua n-2 đỉnhtrung gian
Tổng các đường đi không đi qua đỉnh trung gian nào là 1 (chính là đườngnối trực tiếp 2 đỉnh đó) Tổng các đường đi đi qua 1 đỉnh trung gian là sốcách chọn có thứ tự 1 đỉnh trong số n-2 đỉnh còn lại (A1 n-2), tổng cácđường đi đi qua 2 đỉnh trung gian là số cách chọn có thứ tự 2 đỉnh trong
số n-2 đỉnh còn lại (A2 n-2), tổng các đường đi qua 3 đỉnh trung gian là sốcách chọn có thứ tự 3 đỉnh trên n-2 đỉnh còn lại (A3 n-2), …, tổng cácđường đi qua n-2 đỉnh trung gian là số cách chọn có thứ tự n-2 đỉnh trênn-2 đỉnh còn lại (An-2 n-2).
Qua công thức trên ta thấy S là một số rất lớn (với N = 1000 thì S có tới
2563 chữ số), không có kiểu số nào có thể lưu trữ được, ta phải sử dụngkiểu dữ liệu khác Tốt nhất trong trường hợp này ta nên sử dụng mảngkiểu longint, với mỗi phần tử mảng sẽ lưu 1 số có 6 chữ số của chữ số S
Để đơn giản trong xử lý ta nên lưu ngược, a[1] sẽ chứa 6 chữ số cuối cùngcủa S, a[2] sẽ chứa 6 chữ số tiếp theo, …
PCOUNT.INP PCOUNT.OUT
S:=1;
For i:=1 to n-2 do Begin
S:= S*i;
S:=S+1;
End;
Trang 14Ví dụ: S = 1234031809283756
Ta có mảng A như sau: a[1] = 283756; a[2] = 031809; a[3] = 001234.Nhưng thực tế a[2] = 31809; a[3] = 1234 (loại bỏ các số 0 ở đầu trái), vậy khighi kết quả ra file ta phải xử lý thêm chỗ này để đạt được kết quả đúng (thêm 0vào trước cho đủ 6 chữ số rồi mới ghi ra file kết quả)
s:string; f:text; ok:boolean;
a:array[1 500] of longint;{mang chua
if n>1 then Begin str(a[m],s); write(f,s);
For i:=m-1 downto 1 do Begin
str(a[i],s);
if i<>6 then Begin l:=length(s);
while l<>6 do Begin insert('0',s,1); l:=l+1; End;
End;
write(f,s);
End;
End else Write(f,'0');
close(f);
End;
BEGIN Khoi_tao;
Thuc_hien;
In_ra;
END.
Trang 152.4 Bài toán 4 (Đề thi OLP 2004)
Gọi X là tập tất cả các xâu nhị phân không có 2 bit 1 liền nhau Các xâuđược đánh thứ tự theo trình tự được sinh ra (từ nhỏ
đến lớn, bit 0 đứng trước bit 1) Chẳng hạn với n=5 ta
có các xâu sau:
Bài toán đặt ra: Hãy xác định xâu nhị phân n bit ứng
với số thứ tự m cho trước Hạn chế: n<=200
Dữ liệu: Nhập từ file văn bản Nhiphan.Inp gồm:
Dòng đầu tiên là số nguyên dương n (n<= 200)
Dòng thứ hai là số thứ tự m
Kết quả: Ghi ra file Nhiphan.Out xâu nhị phân n bit
tương ứng với số thứ tự m Ví dụ
Thuật toán
Gọi L[k] là số các xâu nhị phân như
vậy có k bit Nếu bit thứ k của nó là bit
0 thì k-1 bit còn lại là tự do (tức là ta có
L[k-1] dãy) Nếu bit thứ k của nó là bit
1 thì bit k-1 phải là 0, và k-2 bit còn lại
tự do Vậy ta có: L[k]=L[k-1]+L[k-2] Trong đó L[1] = 2, L[2] = 3
Từ công thức đó ta xác định được số các xâu có n bit Để xác định xâu nhị
phân n bit có thứ tự m cho trước ta có nhận xét: nếu m>L[n-1] thì nhất định bit thứ n phải là 1 Xâu n-1 bit còn lại sẽ có thứ tự là m-L[n-1] Ngược lại thì bit thứ n là bit 0 và xâu n-1 bit còn lại có thứ tự là m.
Do đó ta có thể làm như sau:
For i:= n downto 1 do
If m<=L[i-1] then x[i]:=0 Else
x[i]:=1; m:= m – L[i-1];
1234567
00000000010001000100001010100001001
…
Nhiphan.inp Nhiphan.out
55
00101
Trang 16End;
Tuy nhiên n có thể bằng 200 nên m và các giá trị L[i] có thể xấp xỉ 2200tức là cỡ 1070 (vì 210 xấp xỉ 103) Ta không thể dùng các kiểu số có sẵn màphải sử dụng kiếu số lớn và các phép toán trên số lớn
var i,t:byte;
begin
if na>=nb then begin max:=na; for i:=nb+1 to na do b[i]:=0; end
else begin max:=nb; for i:=na+1 to nb do a[i]:=0; end;
Trang 17end;
if t>0 then begin max:=max+1; kq[max]:=t; end;
end;
{===============================================} Function sosanh(var a,b:bigint; na,nb:byte):boolean;
var i:byte;
Begin
sosanh:=true;
if na<nb then exit
else if na>nb then begin sosanh:=false; exit; end
else
for i:=na downto 1 do
if a[i]<b[i] then exit
else if a[i]>b[i] then begin sosanh:=false; exit; end;
End;
{===============================================} procedure tru(var a,b:bigint; var na,nb:byte);
var i,nho,max:longint;
begin
if na >=nb then begin max:=na; for i:=nb+1 to na do b[i]:=0; end
else begin max:=nb; for i:=na+1 to nb do a[i]:=0; end;
var i,j:integer;
Begin
For i:=3 to n do cong(L[i-1],l[i-2],L[i],cd[i-1],cd[i-2],cd[i]);