Tải bản đầy đủ (.doc) (22 trang)

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

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (166.22 KB, 22 trang )

MỤ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 ……………………………………………….
B. NỘI DUNG
I. Cơ sở lí luận ………………………………………………………………
II. Thực trạng vấn đề trước khi áp dụng …………………………………….
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………………………………………………
1.2.
Xây dựng các phép toán số học ………………………………………
1.2.1 Phép so sánh ………………………………………………………….
1.2.2 Phép cộng một số lớn với một số nhỏ ………………………………..
1.2.3. Cộng hai số nguyên lớn ………………………………………………
1.2.4. Phép trừ ……………………………………………………………….
1.2.5. Phép nhân một số lớn với một số nhỏ ………………………………..
1.2.6. Phép nhân hai số nguyên lớn …………………………………………
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)) ………………………………………….
1.2.8. Phép chia hai số nguyên lớn (lấy phần thương nguyên (div) và dư
(mod)) ……………………………………………………………………….
Phần hai: MỘT SỐ BÀI TOÁN ỨNG DỤNG XỬ LÝ SỐ LỚN ……….....
2.1. Bài toán 1 Tính giai thừa ……………………………….………………
2.2. Bài toán 2 (Đề Bảng C Tin học trẻ không chuyên toàn quốc) ….………
2.3. Bài toán 3 (Đề thi OLIMPIC Tin học sinh viên Việt Nam 2005) ….…..
2.4. Bài toán 4 (Đề thi OLP 2004) ……………………………………….....
Phần ba: bài tập luyện tập ………………………………………………. …


IV. Hiệu quả của sáng kiến ………………………………………………….
C. KẾT LUẬN, KIẾN NGHỊ ……………………………………………...
I. Kết luận …………………………………………………………………...
II. Kiến nghị …………………………………………………………………

2
2
2
2
2
3
3
3
3
3
5
5
5
6
6
7
7
8
8
9
9
10
12
15
18

20
21
21
21

1


A. 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 cho
biế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 8
byte 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ép
tí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ới
cá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ài
toá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ép
toá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ểu
diễ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ài
toá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ỌC
LẶ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ến
kinh nghiệm.

2


B. 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ớn
nhấ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 qua
giớ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.
2. Khó khăn
Chất lượng đầu vào không cao, ở trường THCS còn học sơ sài kiến thức tin
học, Ngôn ngữ Pascal là ngôn ngữ khó trừu tượng đối với học sinh,
Những bài toán nâng cao còn khá khó đối với khả năng của học sinh dẫn đến
học sinh cảm thấy sợ khi gặp những dạng toán đó, từ đó không yêu thích bộ
mô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ố
nguyên lớn.
Danh sách liên kết các số: Sử dụng danh sách liên kết các chữ số (hoặc một
nhóm chữ số)
Ở đây, ta xét cách biểu diễn thứ hai, biểu diễn số nguyên lớn bằng mảng, mỗi
phần tử của mảng là một chữ số của số và chỉ xét với các số nguyên lớn không
âm.
Khai báo
Const Maxn=1000;
Type
Chuso=0..9;
Bigint=Array[1..maxn] of chuso;
Var
X,a,b: Bigint;

3


Để 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ột
dò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:
procedure readf;
var
s:string; so:chuso; code,i,n:integer;
begin
assign(f,fi); reset(f);
ma:=0; fillchar(a,sizeof(a),0); {a là số nguyên lớn}
mb:=0; fillchar(b,sizeof(b),0); {b là 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='';
close(f); reset(f);
n:=ma; {ma là độ dài của số a}
repeat
readln(f,s);
for i:=1 to length(s) do begin val(s[i],so,code); a[n]:=so; n:=n-1; end;
until s='';
n:=mb; {mb là độ dài của số b}
repeat
readln(f,s);
for i:=1 to length(s) do begin val(s[i],so,code); b[n]:=so; n:=n-1; end;
until s='';
close(f);
end;

Thủ tục đọc file dữ liệu vào gồm một số nguyên lớn và một số nguyên nhỏ
procedure readf;
var s:string; so:chuso; code,i,n:integer;
begin

assign(f,fi); reset(f);
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;
4


repeat
readln(f,s);
for i:=1 to length(s) do begin val(s[i],so,code); a[n]:=so; n:=n-1; end;
until s='';
readln(f,num); {num là số nguyên nhỏ}
close(f);
end;

1.2. Xây dựng các phép toán số học
1.2.1. Phép so sánh
Để 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ị 1
nế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 maElse
For i:= ma downto 1 do
If a[i]>b[i] then begin cmp:=1; exit; end
Else if a[i]

Cmp:=0;
End;

1.2.2. Phép cộng một số lớn với một số nhỏ
Thuật toán: cộng một số nguyên lớn a với một số nhỏ num kiểu longint.
 Tong:=a; nho:=num;
 Thực hiện lặp:
- Cộng từng chữ số (cộng từ bậc thấp đến bậc cao, có nhớ). t:=a[k]
+nho;
- 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
tong:bigint);
var t,nho,k:longint;
begin
tong:=a; nho:=num; {đặt số cần cộng vào biến
nho}

while nho>0 do
begin
ma:=ma+1;
tong[ma]:=nho mod 10;
nho:=nho div 10;
5


for k:=1 to ma do
begin

t:=a[k]+nho;
nho:=t div 10;
tong[k]:=t mod 10;
if nho=0 then break;
end;

end;
end;

Chú ý: 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úc như
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ố.
1.2.3. Cộng hai số nguyên lớn
Thuật toán: Như phép cộng thông thường đã học và thực hiện trên giấy. (cộng các
chữ số từ bậc thấp đến bậc cao, có nhớ).

Procedure
cong( a,b:bigint;Var
begin
tong:bigint );
t:=t+a[i]+b[i];
var i,t:longint;
tong[i]:=t mod 10;
begin
t:=t div 10;
if ma >=mb then begin max:=ma; for
end;
i:=mb+1 to ma do b[i]:=0; end

if t>0 then begin max:=max+1;
else begin max:=mb; for i:=ma+1 to tong[max]:=t; end;
mb do a[i]:=0; end;
end;
t:=0; fillchar(tong,sizeof(tong),0);
for i:=1 to max do

1.2.4. Phép trừ
Thuật toán: dùng thuật toán trừ kiểu thủ công (trừ các chữ số từ bậc thấp đến bậc
cao, có nhớ). Có hai trường hợp:
- Trường hợp a>b thì thực hiện a trừ cho b
- Trường hợp aProcedure
tru (a,b:bigint; var
begin
hieu:bigint);
nho:=a[i]-b[i]-nho;
var i,nho:longint;
if nho<0 then begin
begin
hieu[i]:=10+nho; nho:=1; end
if ma >=mb then begin max:=ma;
else begin hieu[i]:=nho;
for i:=mb+1 to ma do b[i]:=0; end
nho:=0; end;
else begin max:=mb; for i:=ma+1
end;
to mb do a[i]:=0; end;
{xoa so 0 o dau}
nho:=0;

while (max>1) and (kq[max]=0)
6


for 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;
while nho>0 do
var tich:bigint);
begin
var t,nho,k:longint;
max:=max+1;
begin
tich[max]:=nho mod 10;
nho:=0;
nho:=nho div 10;
for k:=1 to max do {max là độ dài của
end;
số lớn x}
end;
begin
t:=num*x[k]+nho;
nho:=t div 10;

tich[k]:=t mod 10;
end;

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ụ:
Theo trên ta thấy việc nhân hai số gồm hai thao tác:
352
×
Thao tác 1: Thực hiện việc nhân mỗi chữ số (từ phải sang
18
trái cho đến hết) của số thứ hai với số thứ nhất.
2816
Thao tác 2: Thực hiện phép cộng tất cả các số vừa tính
+
được ở trên.
352
procedure
6336

procedure nhan2so(a,b:bigint; var tich:bigint); {tích hai số lớn}
var x:bigint; j,k,l:integer;
begin
j:=0;fillchar(kq,sizeof(kq),0);
for k:=1 to mb do
begin
nhan(a,b[k],tich1); {tích của số lớn a với từng chữ số của b}
for l:=1 to j do chen(0,tich1);
cong(kq,tich1,tich); {cộng dồn các số tính được}
kq:=tich;

j:=j+1;
7


end;
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;
num:longint;var
thuong:bigint;var thuong[i]:=nho div num; end;
du:longint);
nho:=nho mod num;
var nho,k,i,j:longint;
end;
begin
du:=nho; max:=i;
nho:=0; k:=ma; i:=0;
{Xóa số 0 ở đầu của số thuong}
while k>=1 do
while (thuong[1]=0) and (max>1) do
begin
begin

while (nho<num) and (k>=1) do
for j:=1 to max-1 do
begin
thuong[j]:=thuong[j+1];
nho:=nho*10+a[k];
max:=max-1;
if nhoend;
thuong[i]:=nho div num; end;
end;
k:=k-1;
end;

1.2.8. Phép chia hai số nguyên lớn (lấy phần thương nguyên (div) và dư (mod))
Thuật toán: Thực hiện phép chia như chia bình thường trên giấy, bằng cách trừ
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êm
phầ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 chia
cá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);
var k,i,t,j:integer;
begin
k:=ma; i:=0;max:=0;
while k>=1 do
begin
while (not ss(du,b)) and (k>=1) do {cat lan luot tung doan cua so bi chia}
8



begin
chen(a[k],du);
if not ss(du,b) then begin i:=i+1;thuong[i]:=0; end;
dec(k);
end;
if ss(du,b) then {hàm ss(du,b) trả về giá trị true nếu du>=b}
begin
t:=0; {dem chia bang phep tru}
while ss(du,b) do
begin
inc(t);
tru(du,b);{thủ tục tru(a,b)thực hiện trừ du cho b, kết quả trả lại lưu vào
du}
end;
inc(i);thuong[i]:=t;
end;
end;
m:=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
begin
for j:=1 to m-1 do thuong[j]:=thuong[j+1];
m:=m-1;
end;
end;

Phần hai
MỘT SỐ BÀI TOÁN ỨNG DỤNG XỬ LÝ SỐ LỚN

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ặc
mả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ố
x (x<=2000).
Chương trình
uses crt;
const maxn=1000;
type

while nho>0 do
begin
inc(max);
9


chuso=0..9;
bigint=array[1..maxn] of chuso;
var kq,tich:bigint; max,n,i:integer;
procedure nhan(x:bigint; num:longint;
var tich:bigint);
var t,nho,k:longint;
begin
nho:=0;
for k:=1 to max do
begin
t:=num*x[k]+nho;
nho:=t div 10;

tich[k]:=t mod 10;
end;

tich[max]:=nho mod 10;
nho:=nho div 10;
end;
end;
Begin
clrscr;
write('Nhap N:'); readln(n);
fillchar(kq,sizeof(kq),1);max:=1;
for i:=1 to n do begin nhan(kq,i,tich);
kq:=tich; end;
for i:=max downto 1 do write(kq[i]);
readln;
end.

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 đều
nhỏ 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.
Ví dụ

DAYSO.INP


DAYSO.OUT

41

12

Thuật toán: Tạo dần các số của dãy thỏa mãn tính chất trên. Do giá trị của số
nguyên K rất lớn (255 chữ số). Vì vậy để tạo các số của dãy ta cần sử dụng phép
toán cộng hai số lớn.
const

fi='dayso.inp';
go='dayso.out';

type
bigint=array[1..1000] of byte;
var
a,b,c,d,k:bigint;
S:array[1..1000] of char;
dem,p,l:integer; thoat:boolean; f,g:text;
10


procedure Openf;
begin
assign(f,fi); reset(f); assign(g,go); rewrite(g);
end;
{================================================}
procedure Closef;
begin

close(f); close(g);
end;
{=============================================}
procedure Input;
var i:integer;
begin
l:=0;
while not eoln(f) do
begin
l:=l+1;
read(f,s[l]);
end;
for i:=1 to l do k[i]:=ord(s[l-i+1])-48;
end;
{=============================================}
procedure cong(a,b:bigint; var c:bigint);
var i,t:longint;
begin
t:=0;
for i:=1 to p do
begin
t:=t+a[i]+b[i];
c[i]:=t mod 10;
t:=t div 10;
end;
if t>0 then begin p:=p+1;c[p]:=t;end;
end;
{==============================================}
function Lonhon:boolean;
var i:integer;

begin
if pelse if p>l then begin Lonhon:=true; exit; end
else
11


for i:=p downto 1 do
if k[i]>d[i] then begin lonhon:=false; exit; end
else if k[i]lonhon:=false;
end;
{===============================================}
procedure Main;
var i:integer;
Begin
fillchar(a,sizeof(a),0); fillchar(b,sizeof(b),0);
fillchar(c,sizeof(c),0); fillchar(d,sizeof(d),0);
a[1]:=1;b[1]:=1; c[1]:=1; p:=1; dem:=3;
repeat
inc(dem);
thoat:=false; cong(a,c,d);
a:=b; b:=c; c:=d;
if Lonhon then thoat:=true;
until thoat;
end;
{==============================================}
Procedure Print;
Begin writeln(g,dem-1); end;
BEGIN

Openf; input; Main; Print; closef;
END.

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ạng
giao 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ối
giữa hai nút tô đen.

12


Hình 1

Hình 2

Dữ liệu: Nhập từ file văn bản PCOUNT.INP gồm một số nguyên dương N
(N<= 1000)
Kết quả: Ghi ra file PCOUNT.OUT gồm một dòng chứa số các đường đi khác
nhau đếm được.
Ví dụ
PCOUNT.INP PCOUNT.OUT
Thuật toán:
4
5
 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ài
bằng n-1. Cũng chính là tổng các đường đi không đi qua đỉnh trung gian
nào, đi qua 1 đỉnh trung gian, qua 2 đỉnh trung gian, …, qua n-2 đỉnh
trung gian.
 Tổng các đường đi không đi qua đỉnh trung gian nào là 1 (chính là đường
nố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 (A 1n-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 (A2n-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 (A 3n-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ên
n-2 đỉnh còn lại (An-2n-2).
Như vậy:
S = 1+ A1n-2 + A2n-2 + A3n-2 + … + An-2n-2 (1)
(1) = 1+ (n-2) + (n-2)(n-3) + (n-2)(n-3)(n-4)+ … + (n-2)! (2)
(2) = 1+ (n-2)[1+ (n-3)[1+(n-4)[…[1+1]…]]] (3)
(3) = ((…(1+1)2+1)3+1)4+1)5+1)…(n-3)+1)(n-2)+1 (4)
Như vậy từ công thức (4) ta có thuật toán theo đoạn mã sau:
S:=1;
For i:=1 to n-2 do
Begin
S:= S*i;
S:=S+1;
End;
13


Cấu trúc dữ liệu:
 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ụng
kiểu dữ liệu khác. Tốt nhất trong trường hợp này ta nên sử dụng mảng
kiể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ùng
của S, a[2] sẽ chứa 6 chữ số tiếp theo, …
Ví 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 khi
ghi kết quả ra file ta phải xử lý thêm chỗ này để đạt được kết quả đúng (thêm 0
vào trước cho đủ 6 chữ số rồi mới ghi ra file kết quả).
Chương trình
Const
stout='PCOUNT.OUT';
stinp='PCOUNT.INP';
k=1000000; d=6;
Var i,j,n,m,l,nho:longint;
s:string; f:text; ok:boolean;
a:array[1..500] of longint;{mang chua
day so}
procedure Khoi_tao;
Begin
assign(f,stinp); reset(f);
read(f,n); close(f);
a[1]:=1; m:=1; {m là số phần tử của mảng
a}
end;
{===========================
}
Procedure Thuc_hien;
Begin

For i:=1 to n-2 do
Begin
nho:=0; {Nhân số lớn a với số i, i<1000}
For j:=1 to m do
Begin
nho:=nho+a[j]*i;
a[j]:=nho mod k;

Procedure In_ra;
Begin
Assign(f,stout); Rewrite(f);
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;
14


nho:=nho div k;
END.
End;
if nho>0 then Begin m:=m+1;
a[m]:=nho; End;
{Cộng số lớn a với 1}
nho:=1;ok:=true; j:=0;
while ok do
begin
j:=j+1;
nho:=a[j]+nho;
a[j]:=nho mod k;
nho:=nho div k;
if nho=0 then ok:=false;
end;
if j>m then m:=j;
End;
End;

2.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
đượ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.

bit 1 liền nhau. Các xâu
STT

Xâu

1
2
3
4
5
6
7

00000
00001
00010
00100
00101
01000
01001
….

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ư Nhiphan.inp Nhiphan.out
vậy có k bit. Nếu bit thứ k của nó là bit
5
00101
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 5
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[k1]+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
15


đị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
Begin
x[i]:=1; m:= m – L[i-1];
End;

 Tuy nhiên n có thể bằng 200 nên m và các giá trị L[i] có thể xấp xỉ 2 200
tứ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.
Chương trình
const
maxn=100; fi='nhiphan.inp'; fo='nhiphan.out';

type
chuso=0..9;
bigint=array[1..maxn] of chuso;
var m:bigint; n:integer;mm:byte; f:text;
L:array[0..200] of bigint;
cd:array[0..200] of byte; {chiều dài của mỗi số L[i]}
x:array[1..201] of byte;
procedure readf;
var s:string; so:chuso; code,i:integer;
begin
assign(f,fi); reset(f); readln(f,n);
Fillchar(cd,sizeof(cd),0);
read(f,s); mm:=length(s);
for i:=0 to 200 do fillchar(l[i],sizeof(l[i]),0);
L[0][1]:=1; L[1][1]:=2; L[2][1]:=3;
cd[0]:=1; cd[1]:=1; cd[2]:=1;
for i:=1 to mm do
begin
val(s[i],so,code);
m[mm-i+1]:=so;
end; close(f);
end;
{================================================}
procedure cong(var a,b,kq:bigint; var na,nb,max:byte);
16


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;
t:=0;
for i:=1 to max do
begin
t:=t+a[i]+b[i];
kq[i]:=t mod 10;
t:=t div 10;
end;
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 naelse if na>nb then begin sosanh:=false; exit; end
else
for i:=na downto 1 do
if a[i]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;
nho:=0;

for i:=1 to max do
begin
nho:=a[i]-b[i]-nho;
if nho<0 then begin a[i]:=10+nho; nho:=1; end
else
begin a[i]:=nho; nho:=0; end;
end;
{xoa so 0 o dau}
17


while (max>1) and (a[max]=0) do dec(max);
na:=max;
end;
{===============================================}
Procedure Process;
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]);
for i:=n downto 1 do
If sosanh(m,L[i-1],mm,cd[i-1]) then x[i]:=0
else
Begin x[i]:=1; tru(m,l[i-1],mm,cd[i-1]); end;
End;
procedure writef;
var i:integer;
begin
assign(f,fo); rewrite(f);
for i:=n downto 1 do write(f,x[i]);
close(f);

end;
{===============================================}
BEGIN
readf; process; writef;
END.

Phần 3: Bài tập luyện tập
Bài 1. Tính MN (M, N<=100).
Bài 2. Tính tổ hợp chập k của N (kBài 3. Xây dựng hàm kiểm tra số nguyên dương N có phải là số chính phương
không (N<10100)?
Bài 4. Món quà đầu năm
Huda là vùng nổi tiếng trên thế giới với sự hiện đại kết hợp với sự huyền
bí của mình. Mỗi người ở đây có một con số riêng của mình và không ai giống
ai cả (như ‘số hiệu’ vậy). Ở Huda 3, 5, 7 được coi là những con số đem lại sự
may mắn, vì vậy mỗi khi năm mới về mọi người đều tặng và nhận quà tập thể
bằng cách rất độc đáo mang ‘dấu ấn’ của những con số này.
Các món quà được đánh số ‘may mắn’ từ nhỏ đến lớn và đặt ở quảng
trường lớn. Số ‘may mắn’ là số nguyên dương chỉ chia hết cho 3, 5 và 7 mà
không chia hết cho số nguyên tố nào khác (nghĩa là có dạng 3 i * 5j *7k). Số món
quà đúng bằng số người ở đây nên mỗi người được nhận đúng một món quà
18


tương ứng với ‘số hiệu’ của mình về thứ tự từ nhỏ đến lớn (người có ‘số hiệu’
lớn thứ N thì sẽ nhận món quà có số lớn thứ N). Những người có ‘số hiệu’ 1, 2,
3, 4 … sẽ nhận những món quà ‘may mắn’ tương ứng có số là 3, 5, 7, 9, … Giao
thừa nào cả Huda cũng nhộn nhịp, mọi người đổi ‘số hiệu’ cho nhau chỉ trước
vài giờ nhưng rồi ai cùng nhanh chóng tìm ra món quà may mắn cho mình. Tuy
nhiên những người mới đến Huda thì rất bối rối không biết từ ‘số hiệu’ của mình

bằng cách nào tìm ra số may mắn của món quà (hàng năm có trên dưới 1000
người mới tới Huda).
Bạn có thể giúp họ nhanh chóng tìm ra món quà may mắn của mình
không? Bằng chiếc máy tính với ngôn ngữ lập trình yêu thích Trong Email họ
gửi HelpUs.txt có chứa ‘số hiệu’ của họ (nhỏ hơn 30000), có dạng như sau: số
đầu tiên là số người mới đến Huda, các số tiếp theo là ‘số hiệu’ của họ.
Bạn hãy lập trình đưa ra file ForYou.txt chứa các số may mắn trên món
quà của họ, các số cách nhau bởi dấu cách.
Một năm mới của Huda sẽ may mắn hơn nhờ chương trình của bạn đó!
Ví dụ
Hướng dẫn
HelpUs.txt
ForYou.txt
Chúng ta phân tích lại đầu bài một cách 5
9 15 21 25 27
đơn giản như sau:
45678
- Mỗi người ở Huda có một ‘Số hiệu’ là
N trong khoảng từ 1 đến 30000 và tương ứng với số hiệu đó là món quà may
mắn có số lớn thứ N trong dãy số quà may mắn có dạng 3 i * 5j *7k (i,j,k>=0;
i*j*k<>0) . Như vậy yêu cầu của bài toán là tìm số lớn thứ N trong dãy số
dạng 3i * 5j *7k (dãy 3, 5, 7, 9, 15, …)
- Sinh ra dãy một cách bất kỳ sao cho các phần tử của dãy có dạng 3 i * 5j *7k
và sắp tăng dần dãy, chẳng hạn ta tạo được dãy A[1], A[2], … khi đó người
có số hiệu thứ N sẽ nhận món quà may mắn là A[N]. Với N<=30000, con số
may mắn lớn nhất sẽ là một số rất lớn với 37 chữ số. Áp dụng giải thuật sinh
như sau:
Mục đích: Tạo ra dãy A[1], A[2], A[3], … tăng dần đến N=30000
Nguyên tắc: Sinh ra số A[N] dựa vào các số trước A[1], A[2], …,
A[N-1] sao cho A[N] đảm bảo là gần A[N-1] nhất.

Nhận xét: A[N] bằng tích của 3, 5 hoặc 7 với một trong các số đã biết, sao cho
A[N] nhỏ nhất. Hay A[N]= min{3*A[i], 5*A[i], 7*A[i]}, i = 1..n.
Tuy nhiên chúng ta không cần phải thử i từ 1 đến N như vậy, chúng ta dùng
3 chỉ số i3, i5, i7 cho việc tìm min này – chúng được tăng lên theo từng bước
tìm A[N] mà không cần kiểm tra từ 1 như sau:
A[0]:=1;
i3:=0; i5:=0; i7:=0;
For i:=1 to N do
Begin
19


While 3*A[i3]<=A[i-1] do inc(i3);
While 5*A[i5]<=A[i-1] do inc(i5);
While 7*A[i7]<=A[i-1] do inc(i7);
A[i]:=min(3*A[i3],5*A[i5],7*A[i7]);
End;

Sau khi đã có dãy A[1..30000] được “tính sẵn” chúng ta dễ dàng làm
phần còn lại. Trong chương trình còn phải xử lý mảng động, số lớn.
Bài 5. Bài Gard
Một toán công nhân gồm N người đánh số hiệu từ 1 đến N được bố trí
làm việc trên một toà nhà có N tầng. Do tính chất của công việc nên các công
nhân phải được bố trí làm việc theo các tầng liên tục từ tầng 1 và mỗi tầng
không quá K người (K≤ N) (như vậy trên các tầng cao có thể không có công
nhân làm việc).
Yêu cầu: Xác định xem có bao nhiêu cách khác nhau bố trí công nhân làm việc
trên các tầng thoả mãn yêu cầu nói trên. Hai cách bố trí xem như khác nhau nếu
trong hai cách bố trí đó có ít nhất một công nhân được phân công khác nhau.
Ví dụ: N=3, K=2 có tất cả 12 cách bố trí theo bảng dưới đây: trong bảng, từ cột thứ hai các ô

ghi tên các công nhân làm việc trên tầng ghi ở cột 1.

Tầng 1

2

3

4

5

6

7

8

9

10

11

12

1

12


13

32

1

2

3

1

1

2

2

3

3

2

3

2

1


23

31

12

2

3

1

3

1

2

3

2

3

1

2

1


3

Dữ liệu: Fiel văn bản GARD.INP gồm một dòng ghi hai số N, K (1 ≤ K ≤ N ≤
50).
GARD.INP GARD.OUT
Kết quả: Đưa ra file văn bản GARD.OUT một
12
dòng duy nhất chứa số M - số cách bố trí khác 3 2
nhau tìm được. Ví dụ:
Hướng dẫn:
 Thuật toán Quy hoạch động và xử lý số lớn
 Gọi L[i] là số cách bố trí khi có i người. Đầu tiên ta sẽ xếp vào tầng thứ
nhất, giả sử ta ở đây bố trí T người, rõ ràng T = 1..Min(i,k), khi xếp T
người ở tầng 1 sẽ còn (i-t) người và sẽ có L[i-t] cách bố trí (i-t) người còn
lại vào các tầng tiếp theo. Mặt khác ta có C it cách chọn ra T người ở tầng
1. Như vậy L[i] = Cui * L[i-u]; trong đó u = 1..t (t = min(i,k));
 L[i] = S(Cui * L[i-u]) trong đó u = 1..t
 Sau khi xây dựng xong mảng L, kết quả của bài toán là L[n]. Mặt khác do
Ktoán với số lớn để giải quyết.
20


IV. Hiệu quả của sang kiến.
Từ thực tế ôn luyện học sinh giỏi của bản thân những năm gần đây tôi nhận
thấy phương pháp giải quyết có mặt tích cực sau đây: học sinh yêu thích bộ môn
tin học hơn và có thể dễ dàng giải quyết được dạng bài toán có số nguyên lớn là
một trong số những dạng bài toán khó đối với học sinh trước đây. Góp phần giúp
học sinh tự tin hơn.


21


C. KẾT LUẬN, KIẾN NGHỊ
I. Kết luận.
Khi đã nắm vững nhiều dạng thuật giải kết hợp với kỹ năng lập trình tốt
thì chúng ta có thể giải nhiều bài toán tin học khác nhau. Chẳng hạn khi có thuật
toán tốt nhưng do bài toán yêu cầu với dữ liệu rất lớn thì ta phải có kỹ năng làm
việc tốt với số lớn thì sẽ vượt qua các test lớn. Tuy nhiên nếu không biết sử dụng
một cách linh hoạt thì chương trình chạy chậm và tốn nhiều bộ nhớ. Vì vậy cần
có các kỹ thuật xử lý số lớn tốt để giảm bớt các phép tính và biến lưu trữ.
Bản thân là một giáo viên đứng lớp đã từng ôn luyện học sinh giỏi, tôi
luôn trăn trở rằng làm thế nào để nâng cao chất lượng của bộ môn góp phần
nâng cao chất lượng giáo dục chung của nhà trường. Từ đó tôi đã áp dụng
phương pháp như đã trình bày ở trên, ban đầu do học sinh chưa quen nên cũng
gặp khó khăn. Nhưng sau một thời gian thực hiện cũng đã cho kết quả khá khả
quan. Học sinh hứng thú học, ham học, yêu thích môn học, tiếp thu bài tốt hơn
và có thể dễ dàng giải quyết được các bài toán liên quan đến số nguyên lớn.
II. Kiến nghị
Vấn đề tìm kiếm các phương pháp giải quyết các bài toán nâng cao đang
là vấn đề cần thiết. Để có thể ôn luyện tốt cho học sinh giỏi nhằm giúp học sinh
có thể đoạt giải cao trong kì thi học sinh giỏi cấp tỉnh.
Với thực trạng học Tin học trong nhà trường có thể coi đây là một quan
điểm của tôi đóng góp ý kiến. Mong cấp trên tạo điều kiện về cơ sở vật chất để
có thể áp dụng tốt phương pháp trên.
Mặc dù đã cố gắng song không thể tránh được các thiếu sót, rất mong
được sự đóng góp ý kiến của các cấp lãnh đạo, của các bạn đồng nghiệp để đề
tài của tôi được hoàn thiện hơn.
Tôi xin chân thành cảm ơn!


XÁC NHẬN CỦA THỦ TRƯỞNG

Thanh Hóa, Ngày 10 tháng 5 năm 2018

ĐƠN VỊ

Tôi xin cam đoan đây là SKKN của mình viết,
không sao chép nội dung của người khác
NGƯỜI VIẾT ĐỀ TÀI

Nguyễn Thị Diệp

22