You are on page 1of 47

Năm học 2002-2003

Bài 1: CHỮ SỐ tên file chương trình CHUSO.PAS


Xét dãy số tự nhiên {an} đuợc xây dựng theo quy tắc sau:
 Cho trước số a0 là một số tự nhiên có tối đa 10 chữ số.
 Số ai (i>0) là một số tự nhiên nhận được từ ai-1 bằng cách viết thêm vào sau
các chữ số của ai-1 chính ai-1 nhưng viết theo thứ tự ngược lại.
Ví dụ:
Với a0 = 123 thì a1 = 123321, a2 = 123321123321, a3 = 123321123321123321123321
Với hai số N và M cho trước, hãy tìm chữ số thứ M trong aN.
Dữ liệu cho trong file văn bản với tên là CHUSO.INP trong đó dòng đầu chứa số a 0,
dòng thứ hai chứa hai số N và M.
Kết quả ghi ra file văn bản với tên là CHUSO.OUT. Trong trường hợp có lời giải, file
này sẽ chứa số tìm được, ngược lại file này chứa số -1.
Ví dụ:
CHUSO.INP CHUSO.OUT

123 1

37
Giới hạn: 1  N  25, 1  M  1 000 000 000.

Lời giải:
Trước tiên ta nhận xét mặc dù đề bài cho a 0 là số tự nhiên, nhưng vì bài toán không
sử dụng tính chất của số nên ta có thể xem a0 như một xâu ký tự.
Gọi L là số ký tự của a 0, ta thấy a0 có L ký tự, a1 có 2L ký tự, a2 có 4L ký tự, ..., aN có
2NL ký tự.
Ký hiệu sR là chuỗi ký tự đảo ngược của chuỗi s. Ví dụ: nếu s="abca" thì s R ="acba",
các chữ cái được viết theo thứ tự ngược lại. Để ý là với hai chuỗi a, b bất kỳ ta có
(ab)R=bRaR. Theo đề bài, ta có a1 = a0a0R, a2=a1a1R=a0a0R(a0a0R)R=a0a0Ra0a0R, ..., aN=
a0a0Ra0a0R... a0a0R, nghĩa là xâu aN sẽ ghép thành từ các xâu a0 và a0R xen kẽ nhau.
Từ nhận xét này ta có thuật toán sau:
Nếu vị trí M nằm ngoài xâu ký tự a N, hay nói cách khác nếu M < 1 hoặc M > 2NL thì in
ra -1. Đoạn lệnh sau đây thể hiện điều này:
if (m<1) or (m>l*(1 shl n)) then begin {chú ý: 1 shl n = 2^n}
timchuso:=-1;
exit;
end;

Còn nếu không, ta xem vị trí M sẽ thuộc về một xâu a 0 hay một xâu đảo ngược a0R,
điều này được chỉ ra bởi giá trị của biểu thức ((M-1) div L) mod 2 bằng 0 hay 1. Biết
được điều này, sử dụng phép lấy số dư, ta sẽ tìm được vị trí của ký tự cần tìm trong
chuỗi a0:
i:=(m-1) mod l+1; {i là vị trí tương ứng với vị trí M trong chuỗi a0}
if ((m-1) div l) mod 2=1 then i:=l-i+1; {nếu vị trí M thuộc xâu đảo ngược a 0R thì
ta đảo ngược vị trí i}
timchuso:=ord(a0[i])-ord('0'); {kết quả bằng chữ số a0[i]}

1
Chương trình:
const
finp='chuso.inp';
fout='chuso.out';

var
a0: string;
n, m: longint;

function timchuso(n,m: longint): longint;


var l, i: longint;
begin
l:=length(a0);
if (m<1) or (m>l*(1 shl n)) then begin timchuso:=-1; exit; end;
i:=(m-1) mod l+1;
if ((m-1) div l) mod 2=1 then i:=l-i+1;
timchuso:=ord(a0[i])-ord('0');
end;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

readln(a0);
readln(n,m);
write(timchuso(n,m));

close(input);
close(output);
end.

Bài 2: TÍNH DIỆN TÍCH tên file chương trình HCN.PAS


Trên mặt phẳng tọa độ cho N (N  10 000) hình chữ nhật với các cạnh song song
với các trục tọa độ. Các hình chữ nhật được đánh số từ 1 tới N. Hình chữ nhật thứ i
được cho bởi toạ độ đỉnh trái dưới (xi1 , yi1) và tọa độ đỉnh phải trên (xi2, yi2). Các số xi1
, yi1, xi2, yi2 là các số nguyên trong phạm vi từ -100 đến 100.

Hãy lập trình tính:


1. Diện tích của phần mặt phẳng mà N hình chữ nhật này phủ.
2. Tính diện tích phần chung của N hình chữ nhật này.
Dữ liệu cho trong file HCN.INP trong đó dòng đầu chứa số N. Dòng thứ i trong N
dòng tiếp theo chứa 4 số số xi1 , yi1, xi2, yi2.
Kết quả ghi ra file HCN.OUT gồm 2 dòng, trong đó dòng đầu chứa số S 1 là kết quả
của câu 1. Dòng thứ hai chứa số S2 là kết quả của câu 2.

2
Ví dụ:
HCN.INP
HCN.OUT
2
4
0011
1
-1 -1 1 1

3
Bài 3: BẢNG QUẢNG CÁO Tên file chương trình QUANGCAO.PAS
Trên quảng trường trung tâm của thủ đô Rome có một bảng quảng cáo hình
chữ nhật gồm N x M ô vuông. Mỗi ô có một bóng đèn, mỗi bóng đèn có hai trạng thái
tắt hoặc sáng. Ứng với mỗi dòng cũng như mỗi cột có một công tắc. Khi tác động
đến một công tắc nào đó tất cả các bóng đèn trên dòng hoặc cột tương ứng sẽ đổi
sang trạng thái ngược lại (đang sáng thành tắc, đang tắc được bật sáng). Để mừng
đội nhà thắng trận trong trận cầu chiều qua người phụ trách bảng quảng cáo muốn
bảng có được nhiều bóng đèn sáng nhất.Với trạng thái bảng quảng cáo hiện thời
cho trước, người phụ trách nhờ bạn lập trình tìm một phương án tác động lên các
công tắc để nhận được trạng thái bảng quảng cáo mong muốn. Bạn hãy giúp nhà
phụ trách thực hiện điều đó.
Dữ liệu cho trong file văn bản với tên là QUANGCAO.INP trong đó:
 Dòng đầu chứa hai số N và M (: 1  N  10, 1  M  100).
 Dòng thứ i trong N dòng tiếp theo chứa M số 0 hoặc 1. Số thứ j cho biết trạng
thái của bóng đèn thứ j trên dòng thứ i của bảng (1 tương ứng với bóng đèn
sáng, 0 tương ứng với bóng đèn tắt).
Kết quả ghi ra trong file QUANGCAO.OUT trong đó:
 Dòng đầu là số bóng đèn sáng trên bảng tìm được
 Dòng thứ hai chứa S là số lần bạn tác động lên các công tắc.
 S dòng tiếp theo lần lượt ghi ra S công tắc theo trình tự cần bật. Dòng thứ j
trong S dòng này chứa một xâu độ dài không quá 4, ký tự đầu là ‘D’ hoặc ‘C’
tương ứng với tác động thứ i là lên dòng hay cột. Phần còn lại của xâu là chỉ
số của dòng hay cột tương ứng.
Ví dụ:
QUANGCAO.INP QUANGCAO.OUT
4 16
1001 4
0110 C1
0110 C4
1001 D1
D4

Lời giải:
Trước hết ta đưa ra hai nhận xét:
 Mỗi công tắc chỉ cần được tác động nhiều nhất một lần: thật vậy, tác
động một công tắc 2 lần sẽ cho kết quả giống như khi không tác động lên
công tắc.
 Thứ tự tác động lên các công tắc là không quan trọng. Nói cách khác, tác
động một dãy công tắc theo trình tự nào cũng mang lại kết quả như nhau.
Từ hai nhận xét trên, ta thấy mỗi công tắc sẽ có hai khả năng: tác động hoặc không
tác động. Có tất cả M+N công tắc, vậy số khả năng là 2 M+N. Theo giới hạn đề bài ra,
con số này có thể rất lớn. Tuy nhiên để tìm số bóng đèn sáng nhiều nhất, ta chỉ cần
duyệt qua 2N ≤ 210 = 1024 khả năng tác động lên các công tắc trên các hàng của
bảng. Với mỗi khả năng này, đối với mỗi cột ta khẳng định được ngay có cần phải

4
tác động lên công tắc của cột đó hay không: nếu tác động lên công tắc cột mà số
đèn sáng trên cột đó nhiều hơn thì ta sẽ tác động. Đoạn lệnh sau đây thể hiện điều
này. Chú ý trong chương trình dưới đây, ta đánh số cột, hàng bắt đầu từ 0.
for j:=0 to m-1 do begin
dem:=0; {đếm số đèn sáng trên cột j}
for i:=0 to n-1 do
if (b[i,j]=1) then inc(dem);
if (dem<n-dem) then {tác động lên công tắc sẽ cho n-dem bóng đèn sáng}
begin {nếu tác động lên công tắc có lợi hơn:}
batcot(b,j); {tác động!}
inc(t); {tăng biến ghi nhận số lần tác động lên một}
cot[j]:=true; {ghi nhớ rằng đã tác động lên cột j}
end;
end;

Trong 2N khả năng này, ta sẽ chọn ra khả năng cho số bóng đèn sáng nhiều nhất.
Về cài đặt chương trình, chú ý sử dụng kỹ thuật xử lý bit khi duyệt qua 2 N khả năng
tác động lên các công tắc trên hàng. Dùng kỹ thuật này, chương trình sẽ được viết
nhanh và gọn hơn. Câu lệnh sau đây thực hiện điều này:
for s:=0 to (1 shl n)-1 do {biến s là một dãy N bit thể hiện một khả năng}

Do ta muốn có một dãy N bit đến biến s sẽ chạy từ 0 đến 2N - 1.


Để kiểm tra trong khả năng s, một công tắc nào đó có được tác động hay không ta
dùng thủ tục kiểm tra một bit có được bật hay không:
function bitbat(s, i: longint): boolean;
begin
bitbat:=((s shr i) and 1)=1;
end;

Chương trình:
const
finp='quangcao.inp';
fout='quangcao.out';
maxn=15; maxm=110;

type bang=array[0..maxn-1, 0..maxm-1] of byte;

var
n, m, kq, sotacdong: longint;
a, b: bang;
hang, luuhang: array[0..MAXN-1] of boolean;
cot, luucot: array[0..MAXM-1] of boolean;

procedure nhap;
var i, j: longint;
begin
readln(n,m);
for i:=0 to n-1 do
for j:=0 to m-1 do
read(a[i,j]);

5
end;

function bitbat(s, i: longint): boolean;


begin
bitbat:=((s shr i) and 1)=1;
end;

procedure batcot(var a: bang; t: longint);


var i: longint;
begin
for i:=0 to n-1 do a[i,t]:=1-a[i,t];
end;

procedure bathang(var a: bang; t: longint);


var i: longint;
begin
for i:=0 to m-1 do a[t,i]:=1-a[t,i];
end;

function sodensang(var a: bang): longint;


var i,j,dem: longint;
begin
dem:=0;
for i:=0 to n-1 do for j:=0 to m-1 do if (a[i,j]=1) then inc(dem);
sodensang:=dem;
end;

procedure xuly;
var s,i,j,dem,k,t: longint;
begin
kq:=0;
for s:=0 to (1 shl n)-1 do begin
b:=a;
t:=0;
fillchar(hang,sizeof(hang),false);
fillchar(cot,sizeof(cot),false);
for i:=0 to n-1 do
if (bitbat(s,i)) then begin
bathang(b,i); inc(t); hang[i]:=true;
end;
for j:=0 to m-1 do begin
dem:=0;
for i:=0 to n-1 do
if (b[i,j]=1) then inc(dem);
if (dem<n-dem) then begin batcot(b,j); inc(t); cot[j]:=true; end;
end;
k:=sodensang(b);
if (k>kq) then begin
kq:=k;
sotacdong:=t;

6
luuhang:=hang;
luucot:=cot;
end;
end;
end;

procedure xuat;
var i: longint;
begin
writeln(kq);
writeln(sotacdong);
for i:=0 to m-1 do
if (luucot[i]) then writeln('C',i+1);
for i:=0 to n-1 do
if (luuhang[i]) then writeln('D',i+1);
end;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

nhap;
xuly;
xuat;

close(input);
close(output);
end.

Năm học 2003-2004


Bài 1: TỔNG LỚN NHẤT tên chương trình: SUM.PAS
Cho một bảng A gồm N x N số nguyên (N  100), các dòng được đánh số trên
xuống dưới bắt đầu từ 1, các cột được đánh số từ trái qua phải cũng bắt đầu từ 1.
Mỗi số trong bảng có giá trị tuyệt đối không vượt quá 10000. Đường chéo chính của
bảng là đường thẳng nối hai ô (1,1) và (N,N). Như vậy trên bảng có 2N-1 đuờng
chéo song song với đường chéo chính.
Bài toán: Hãy tìm đường chéo song song với đường chéo chính có tổng các phần tử
trên đường chéo đó là lớn nhất.
Dữ liệu vào cho trong file văn bản SUM.INP trong đó:
Dòng đầu chứa số N.
Dòng thứ i trong N dòng tiếp theo chứa N số nguyên lần lượt ứng với các phần tử
nằm trên dòng thứ i của bảng A.
Kết quả ghi ra trong file văn bản SUM.OUT trong đó chứa một số nguyên duy nhất là
tổng các phần tử trên đường chéo mà bạn tìm được.

7
1 2 4 3
Đường chéo
3 4 2 5
2 5 4 3
4 3 2 5
Ví dụ: với bảng A như hình vẽ, đường chéo chính chính là đường chéo có tổng lớn
nhất (bằng 14), các file dữ liệu vào/ra lần lượt có nội dung như sau:
SUM.INP SUM.OUT
4 14
1243
3425
2543
4325

Lời giải
Để giải bài toán này ta cần biết cách duyệt qua các đường chéo song song với
đường chéo chính của bảng số một cách nhanh gọn và hiệu quả. Có rất nhiều cách
để thực hiện điều này. Sau đây là một cách: để ý rằng trên một đường chéo, hiệu
giữa chỉ số hàng và chỉ số cột là không đổi. Hiệu này nhận giá trị từ 1-N (tương ứng
với đường chéo gồm 1 ô góc trên phải (1,N)) cho đến N-1 (tương ứng với đường
chéo gồm 1 ô góc dưới trái (N,1)). Do đó ta lần lượt xét các giá trị hiệu từ 1-N đến N-
1. Ký hiệu giá trị này là T. Với mỗi giá trị T, ta duyệt qua các cột trên đường chéo
tương ứng. Nếu T≥0 (tương ứng với các đường chéo ở nửa dưới của bảng) thì cột
bắt đầu là 1 còn nếu T<0 thì cột bắt đầu là 1-T.
Đoạn lệnh sau thể hiện cách làm này:
for t:=1-n to n-1 do
if (t>=0) then j:=1 else j:=1-t; {j là biến lưu cột bắt đầu của đường chéo}
s:=0; {tổng các phần tử trên đường chéo}
repeat
s:=s+a[j+t,j]; {cộng giá trị ô hiện thời vào tổng}
inc(j); {sang cột mới}
until (j+t>n) or (j>n); {vượt quá phạm vi của bảng}
if (s>kq) then kq:=s; {cập nhật kết quả}
end;

Chương trình:
const
MAXN=105;
finp='sum.inp';
fout='sum.out';

var
n, i, j, t, s, kq: longint;
a: array[1..MAXN, 1..MAXN] of longint;
begin
assign(input, finp);

8
reset(input);
assign(output, fout);
rewrite(output);

readln(n);
for i:=1 to n do
for j:=1 to n do
read(a[i,j]);
kq:=-maxlongint;
for t:=1-n to n-1 do begin
if (t>=0) then j:=1 else j:=1-t;
s:=0;
repeat
s:=s+a[j+t,j];
inc(j);
until (j+t>n) or (j>n);
if (s>kq) then kq:=s;
end;
writeln(kq);

close(input);
close(output);
end.

Bài 2: SẮP XẾP Tên chương trình: SORT.PAS


Cho một dãy X gồm N số nguyên trong phạm vi từ -10000 đến 10000 (1  N 
100000). Hãy sắp xếp dãy số này theo thứ tự giảm dần.
Dữ liệu vào cho trong file văn bản SORT.INP trong đó dòng đầu chứa số N. Dòng
thứ i trong N dòng tiếp theo chứa số thứ i trong dãy X.
Kết quả ghi ra file văn bản với tên SORT.OUT trong đó lần lượt ghi ra các phần tử
của dãy X đã được sắp xếp mỗi số trên một dòng.
Ví dụ:
SORT.INP SORT.OUT
4 5
3 4
4 3
2 2
5

Lời giải:
Phương pháp sắp xếp mà chúng ta dùng là sắp xếp đếm (counting sort). Phương
pháp này tận dụng việc giới hạn của các số cần sắp xếp có thể lưu đủ trong bộ nhớ,
trong bài toán này phạm vi các số là -10000..10000. Ta sẽ dùng mảng dem[-
10000..10000] trong đó dem[x] lưu số lần xuất hiện của số x trong dãy số.

9
Bài toán này được ra với yêu cầu làm trên trình biên dịch Borland Pascal. Với trình
biên dịch cũ này, quản lý bộ nhớ là một điều quan trọng do dung lượng bộ nhớ bị
hạn chế. Chúng tôi sẽ trình bày lời giải bài toán này trên cơ sở bộ nhớ hạn chế đó.
Do mỗi số có thể xuất hiện đến N ≤ 100 000 lần nên mảng dem phải mang kiểu dữ
liệu longint (số nguyên 32 bit) trong Pascal. Trong Borland Pascal, khi khai báo mảng
20000 phần tử longint sẽ bị báo lỗi là không đủ bộ nhớ. Có một cách để giải quyết
điều này:
Khai bảo mảng dem với kiểu dữ liệu word (có giới hạn từ 0..65535) như sau:
var dem: array[-10000..10000] of word;

Ta nhận xét chỉ có nhiều nhất một phần tử của mang dem có thể có giá trị lớn hơn
hoặc bằng 60000, vì tổng số lượng số nhiều nhất là 100 000. Do đó ta quản lý thêm
một biến lưu phần tử đặc biệt có giá trị đếm vượt 60000 này (nếu có). Ta đặt tên
biến này là v. Đoạn lệnh dưới đây đọc vào các số và quản lý dữ liệu:
for i:=1 to n do
begin
readln(x); {đọc vào một số x}
inc(dem[x]); {tăng biến đếm số lần xuất hiện của số x}
if (dem[x]=60000) then {nếu đã có 60000 số x xuất hiện}
begin
v:=x; {lưu lại số x duy nhất này}
dem[x]:=0; {gán lại biến đếm bằng 0 để tránh tràn số}
end;
end;

Đoạn lệnh dưới đây in ra các số đã sắp xếp theo thứ tự giảm dần:
for i:=-10000 downto 10000 do {duyệt qua phạm vi của các số: [-10000,10000])
begin
for j:=1 to dem[i] do {in ra số i với số lần là dem[i]}
writeln(i);
if (v=i) then for j:=1 to 60000 do {nếu i là số đặc biệt thì ta cần in thêm
60000 lần xuất hiện nữa}
writeln(i);
end;

Chương trình:
const finp='sort.inp';
fout='sort.out';

var dem: array[-10000..10000] of word;


n, i, v: longint;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

fillchar(dem,sizeof(dem),0);
readln(n);

10
v:=0;
for i:=1 to n do
begin
readln(x);
inc(dem[x]);
if (dem[x]=60000) then
begin
v:=x;
dem[x]:=0;
end;
end;
for i:=-10000 downto 10000 do
begin
for j:=1 to dem[i] do
writeln(i);
if (v=i) then for j:=1 to 60000 do
writeln(i);
end;
close(input);
close(output);
end.

Bài 3: HÌNH VUÔNG tên chưong trình: SQUARE.PAS


Trên mặt phẳng cho N hình vuông với các cạnh song song với hệ trục toạ độ được
đánh số từ 1 đến N (1  N  2000). Hình vuông thứ i được cho bởi toạ độ góc dưới
trái (xi, yi) và toạ độ đỉnh phải trên là (zi, ti). Toạ độ của các đỉnh là các số nguyên
trong phạm vi -10000 đến 10000. Khoảng cách giữa hai hình vuông A và B được
định nghĩa là độ dài đoạn thẳng ngắn nhất trong số các đoạn thẳng mà một đầu mút
thuộc hình vuông A và đầu mút kia thuộc hình vuông B.

Yêu cầu: Tìm hai hình vuông xa nhau nhất trong số N hình vuông cho trước.
Dữ liệu: Vào từ file văn bản SQUARE.INP:
 Dòng đầu tiên chứa số N.
 Dòng thứ i trong N dòng tiếp theo chứa 4 số xi, yi, zi và ti.
Kết quả: Ghi ra file văn bản SQUARE.OUT trong đó chứa chỉ số của hai hình vuông
xa nhau nhất mà bạn tìm được.
Ví dụ:
SQUARE.INP SQUARE.OUT
3 1 3
1 1 3 3
2 2 5 5
7 1 8 2
Lời giải
Vấn đề quan trọng nhất trong bài toán này là xây dựng thủ tục tính khoảng cách giữa
hai hình vuông một cách hiệu quả. Để viết thủ tục này, ta sẽ phân chia công việc cần

11
thực hiện thành nhiều công việc con. Nhưng trước tiên cần nhận xét rằng, luôn tồn
tại một đoạn thẳng ngắn nhất nối giữa hai hình vuông mà có ít nhất một đầu
mút là một trong các đỉnh của hai hình vuông. Thật vậy, với một đoạn thẳng mà
hai đầu mút đều không phải là đỉnh của hình vuông, ta có thể dời hai đầu mút sao
cho đoạn thẳng mới không dài hơn đoạn thẳng cũ và ít nhất một đầu mút sẽ trở
thành đỉnh của hình vuông.
Vậy thủ tục tính khoảng cách giữa hai hình vuông sẽ được xây dựng từ trên xuống
như sau:
 Khoảng cách giữa hai hình vuông bằng khoảng cách ngắn nhất trong số các
khoảng cách từ một trong 8 đỉnh của hai hình vuông đến hình vuông kia
 Khoảng cách từ một điểm đến một hình vuông bằng khoảng cách ngắn nhất
trong số các khoảng cách từ điểm đó đến bốn cạnh của hình vuông
Khi đã có thủ tục tính khoảng cách giữa hai hình vuông, ta chỉ cần duyệt qua tất cả
các cặp hình vuông và chọn ra cặp có khoảng cách xa nhất. Do giới hạn của bài
toán là số hình vuông N ≤ 2000 nên cách làm này là khả thi, thời gian chạy là O(N 2).
Sau này các bạn sẽ được tiếp cận các lời giải hiệu quả hơn.

Chương trình:
const MAXN=2020;
finp='square.inp';
fout='square.out';

var
x1, y1, x2, y2: array[1..MAXN] of longint;
n, luui, luuj: longint;

function min(a,b: longint): longint;


begin
if a<b then min:=a else min:=b;
end;

function kc(x,a,b: longint): longint;


begin
if (a<=x) and (x<=b) then kc:=0
else if (x<a) then kc:=a-x
else kc:=x-b;
end;

function kcdoan(x,y,y0,x1,x2: longint): longint;


begin
kcdoan:=sqr(y0-y)+sqr(kc(x,x1,x2));
end;

function kchinhvuong(x,y,x1,y1,x2,y2:longint):longint;
var kq: longint;
begin
kq:=kcdoan(x,y,y1,x1,x2);

12
kq:=min(kq,kcdoan(x,y,y2,x1,x2));
kq:=min(kq,kcdoan(y,x,x1,y1,y2));
kq:=min(kq,kcdoan(y,x,x2,y1,y2));
kchinhvuong:=kq;
end;

function kc2hinh(i,j: longint): longint;


var kq: longint;
begin
kq:=kchinhvuong(x1[i],y1[i],x1[j],y1[j],x2[j],y2[j]);
kq:=min(kq,kchinhvuong(x1[i],y2[i],x1[j],y1[j],x2[j],y2[j]));
kq:=min(kq,kchinhvuong(x2[i],y1[i],x1[j],y1[j],x2[j],y2[j]));
kq:=min(kq,kchinhvuong(x2[i],y2[i],x1[j],y1[j],x2[j],y2[j]));
if (i<j) then kq:=min(kq,kc2hinh(j,i));
kc2hinh:=kq;
end;

procedure nhap;
var i: longint;
begin
readln(n);
for i:=1 to n do readln(x1[i], y1[i], x2[i], y2[i]);
end;

procedure xuly;
var i, j, d, xanhat: longint;
begin
xanhat:=-1;
for i:=1 to n-1 do
for j:=i+1 to n do
begin
d:=kc2hinh(i,j);
writeln(d);
if d>xanhat then
begin
xanhat:=d;
luui:=i;
luuj:=j;
end;
end;
end;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

nhap;
xuly;

13
writeln(luui,' ',luuj);

close(input);
close(output);
end.

Năm học 2004-2005


BÀI 1: KHOẢNG CÁCH GIỮA HAI SỐ Tên chương trình: DISTANCE.PAS
Với hai chữ số x và t, khoảng cách của chúng được định nghĩa là số nguyên không
âm nhỏ nhất d(x,y) mà khi cộng thêm d(x,y) vào một chữ số nào đó trong hai chữ số
x,y thì kết quả nhận được là một số nguyên có chữ số hàng đơn vị trùng với chữ số
còn lại. Ví dụ: d(2,5)=3 vì 2+3=5, d(5,1)=4 vì 1+4=5, còn d(1,9)=2 vì 9+2 = 11.
Với hai số nguyên dương X và Y có cùng số lượng chữ số, khoảng cách d(X,Y) giữa
hai số X và Y là tổng khoảng cách giữa các cặp chữ số cùng hàng tương ứng.
Ví dụ d(213,419)=d(2,4) + d(1,1) + d(3,9) = 2 + 0 + 4 = 6.
Bài toán: Cho hai số X và Y có cùng lượng chữ số N (0 < N < 100), hãy tìm khoảng
cách d(X,Y).
Dữ liệu vào từ file văn bản DISTANCE.INP trong đó dòng đầu chứa số X; dòng thứ
hai chứa số Y thỏa mãn dàng buộc của bài toán.
Kết quả ghi ra file văn bản DISTANCE.OUT trong đóchứa một số nguyên duy nhất
là kết quả d(X,Y) tìm được.
Ví du:
DISTANCE.INP DISTANCE.OUT
213 6
419

Lời giải:
Ta xây dựng hàm d(a,b) tính khoảng cách hai chữ số a và b theo định nghĩa của bài
toán, sau đó dùng hàm này để tính tổng khoảng cách giữa các cặp chữ số của hai
số. Hàm d(a,b) được thể hiện qua đoạn lệnh sau:
function d(a,b:byte):byte;
var i: byte;
begin
for i:=0 to 9 do {duyệt qua các chữ số từ 0 đến 9}
if ((a+i) mod 10=b) or {nếu cộng thêm i vào a mà thu được b}
((b+i) mod 10=a) then {hoặc ngược lại...)
begin
d:=i; {trả về chữ số i}
exit;
end;
end;

Chương trình:
const
finp='distance.inp';
fout='distance.out';

14
function d(a,b:byte):byte;
var i: byte;
begin
for i:=0 to 9 do
if ((a+i) mod 10=b) or ((b+i) mod 10=a) then
begin
d:=i;
exit;
end;
end;

var x, y: string; i,s: longint;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

readln(x,y);
s:=0;
for i:=1 to length(x) do
s:=s+d(ord(x[i])-ord('0'),ord(y[i])-ord('0'));
writeln(s);

close(input);
close(output);
end.

BÀI 2: TẠO BẢNG Tên chương trình: TABLE.PAS


Cho một bảng A gồm N x N số nguyên (N  100), các dòng được đánh số từ trên
xuống dưới bắt đầu từ 1, các cột được đánh số từ trái qua phải cũng bắt đầu từ 1.
Mỗi số trong bảng có giá trị tuyệt đối không vượt quá 30000. Bảng B được tạo ra từ
bảng A theo qui tắc sau:
Phần tử của B nằm ở dòng I, cột j có giá trị bằng tổng của các số nằm trong ô
(i,j) và các ô kề nó trong bảng A: Bij = Aij+A(i+1)j+A(i-1)j+Ai(j+1)+Ai(j-1)
Chú ý: Các phần tử nằm ngoài bảng được xem như có giá trị bằng 0.
Bài toán: Cho bảng A. Hãy tạo ra bảng B tương ứng.
Dữ liệu vào cho trong file văn bản TABLE.INP trong đó :
 Dòng đầu chứa số N.
 Dòng thứ i trong N dòng tiếp theo chứa N số nguyên lần lượt ứng với các
phần tử nằm trên dòng thứ i của bảng A.
 Các số trên cùng một dòng cách nhau bởi khỏang trắng.

Kết quả ghi ra file văn bản TABLE.OUT cho biết bảng B tạo được có định dạng cùng
một qui cách với file input, nghĩa là:

15
Dòng đầu chứ số N.
Dòng thứ i trong N dòng tiếp theo chứa N số nguyên lần lượt ứng với các
phần tử nằm trên dòng thứ i của bảng B.
Các số trên cùng một dòng cách nhau bởi khỏang trắng.
Ví dụ:

TABLE.INP TABLE.OUT
4 4
1234 8 12 16 15
5678 21 28 31 25
9876 27 34 31 23
5432 18 20 16 11

Lời giải:
Ta in ra các phần tử của mảng B theo công thức như đề bài nêu:
for i:=1 to n do begin
for j:=1 to n do
write(a[i,j]+a[i,j-1]+a[i-1,j]+a[i,j+1]+a[i+1,j],' ');
writeln;
end;

Khi cài đặt chú ý để khai báo dư ra các phần tử biên trên mảng A và đặt chúng bằng
giá trị 0:
var
a: array[0..MAXN+1, 0..MAXN+1] of longint;{0,n+1 là chỉ số của các hàng, cột biên}
...
fillchar(a,sizeof(a),0); {tất cả các phần tử, bao gồm phần tử biên sẽ bằng 0}

Chương trình:
const
finp='table.inp';
fout='table.out';
MAXN=105;

var
a: array[0..MAXN+1, 0..MAXN+1] of longint;
n,i,j: longint;
begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

fillchar(a,sizeof(a),0);
readln(n);
for i:=1 to n do
for j:=1 to n do

16
read(a[i,j]);
writeln(n);
for i:=1 to n do begin
for j:=1 to n do
write(a[i,j]+a[i,j-1]+a[i-1,j]+a[i,j+1]+a[i+1,j],' ');
writeln;
end;

close(input);
close(output);
end.

BÀI 3: TÌM NGHIỆM Tên chương trình: EQUA.PAS


Xét một phương trình có dạng sau:
x + y+ z =K
trong đó K là một số nguyên dương.
Phương trình này có thể có vô số nghiệm. Tuy nhiên, ở đây người ta chỉ quan tâm
đến các nghiệm (x,y,z) mà trong đó các số x,y,z đều là các số nguyên tố.
Nhắc lại: số tự nhiên p được gọi là số nguyên tố nếu p>1 và p chỉ chia hết cho 1 và
chính nó.
Bài toán: Với số K cho trước (K < 5000), hãy tìm tất cả các bộ số nguyên tố x,y,z (x
 y  z) là nghiệm của phương trình trên hoặc cho biết không có nghiệm thoả mãn
yêu cầu bài toán.
Dữ liệu vào cho trong file văn bản EQUA.INP trong đó chứa duy nhất số K
Kết quả ghi ra file văn bản EQUA.OUT chứa N + 1 dòng (N là số nghiệm tìm được),
trong đó:
 Dòng thứ i trong N dòng đầu tiên chứa 3 số nguyên cho biết bộ nghiệm thứ i
tìm được.
 Dòng thứ N + 1 chứa 3 số 0 cho biết điểm kết thúc file output.
 Các số trên cùng một dòng cách nhau bởi khoảng trắng.

Ví dụ:
EQUA.INP EQUA.OUT
4 000

EQUA.INP EQUA.OUT
7 223
000
Lời giải
Ta kiểm tra các số nguyên tố từ 1 đến 5000 (giá trị lớn nhất của K) rồi lưu vào một mảng:
for i:=2 to MAXK do
if (nguyento(i)) then begin {nếu i là số nguyên tố}
inc(n);
p[n]:=i; {lưu i vào mảng p}

17
nt[i]:=true; {đặt lại cờ đánh dấu cho biết i là số nguyên tố}
end;

Sau đó dùng hai vòng lặp qua mảng này để duyệt qua x và y. Mảng đánh dấu nt dùng để kiểm
tra nhanh xem z=K-x-y có phải là số nguyên tố không:
for i:=1 to n do {i là chỉ số của số nguyên tố x trong mảng p}
for j:=i to n do {j là chỉ số của số nguyên tố y trong mảng p}
begin
z:=k-p[i]-p[j]; {z=k-x-y}
if (nt[z]) and (p[j]<z) then {kiểm tra z có phải số nguyên tố không?}
writeln(p[i], ' ',p[j],' ', z); {in ra x, y, z}
end;

Thời gian chạy của chương trình là O(N2) với N là số lượng số nguyên tố. N vào khoảng 700
nên chương trình hoàn toàn chạy trong thời gian cho phép.

Chương trình:
const finp='equa.inp';
fout='equa.out';
MAXK=5010;

function nguyento(n: longint): boolean;


var i: longint;
begin
nguyento:=false;
for i:=2 to trunc(sqrt(n)) do if n mod i = 0 then exit;
nguyento:=true;
end;

var n, k, i, j, z: longint;
nt: array[2..MAXK] of boolean;
p: array[1..MAXK] of longint;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

fillchar(nt,sizeof(nt),0);

for i:=2 to MAXK do


if (nguyento(i)) then begin
inc(n);
p[n]:=i;
nt[i]:=true;
end;

readln(k);
for i:=1 to n do

18
for j:=i to n do
begin
z:=k-p[i]-p[j];
if (nt[z]) and (p[j]<z) then
writeln(p[i], ' ',p[j],' ', z);
end;
writeln('0 0 0');

close(input);
close(output);
end.

Năm học 2005-2006


Bài 1: Trộn mảng
Cho hai mảng số nguyên dương A và B lần lượt có N và M số (0 < N, M <50 000).
Các phần tử trong cả hai mảng A và B đều được sắp theo thứ tự tăng dần.
Yêu cầu: hãy tạo mảng C gồm N+M phần tử từ tất cả các phần tử của A và B sao
cho các phần tử của C cũng có thứ tự tăng dần.
Dữ liệu cho trong 2 file văn bản có tên A.INP và B.INP.
Dòng đầu của file A.INP chứa số N. Mỗi dòng trong N dòng tiếp theo chứa
1 số nguyên dương ứng với các phần tử của mảng A.
Dòng đầu của file B.INP chứa số M. Mỗi dòng trong M dòng tiếp theo chứa
1 số nguyên dương ứng với các phần tử của mảng B.
Kết quả xuất ra file văn bản C.OUT gồm N+M dòng, lần lượt chứa các phần tử của
mảng C.
Ví dụ:

A . INP
3
1
2
5

B . INP
2
2
4

C . OUT
1
2
2
4

19
5

Lời giải
Trong bài toán này ta hoàn toàn không cần lưu trữ mảng A và B vào bộ nhớ. Thuật
toán của ta sẽ đọc lần lượt các số từ hai mảng A, B, xử lý và in ra trực tiếp mảng C.
Ta quản lý hai biến chạy i và j tương ứng trên mảng A và B. Ban đầu i và j được gán
giá trị 1, nghĩa là vị trí đầu tiên của mảng. Tại một thời điểm, biến chạy i, j cho biết ta
đã đọc đến phần tử Ai và Bj. Quy tắc tăng giá trị biến chạy là như sau:
 Nếu Ai ≤ Bj: in ra giá trị Ai, tăng biến chạy i, đọc giá trị mới từ mảng A. Đoạn
mã sau đây thể hiện điều này:
{biến a, b chỉ phần tử đang xét Ai, Bj trên hai mảng}
if (a<=b) then {điều kiện Ai ≤ Bj}
begin
writeln(fc,a); {in ra Ai như là phần tử tiếp theo của mảng C}
inc(i); {tăng biến i}
if (i<=n) then {nếu vẫn chưa đọc hết mảng A}
readln(fa,a) {đọc số mới}
else
a:=maxlongint; {nếu đã đọc hết mảng A, gán a giá trị bằng ∞ để sau này
khi so sánh, chỉ các phần tử của mảng B mới được xét}
end else begin
... {tương tự cho trường hợp Ai > Bj}
end;

 Nếu Ai > Bj: tương tự ta in ra giá trị B j, tăng biến chạy j và đọc giá trị mới từ
mảng B
Thuật toán kết thúc khi ta đã đọc hết các số, nghĩa là khi hai biến chạy đều vượt ra
khỏi phạm vi của mảng: i > N và j > M. Ta có thể dùng vòng lặp while sau đây để
kiểm tra điều kiện này:
while (i<=n) or (j<=m) do begin
...
end;
Thời gian chạy của thuật toán là tuyến tính theo số phần tử của hai mảng: O(M+N).

Chương trình:
var fa,fb,fc: text;
n, m, i, j, a, b: longint;
begin
assign(fa,'a.inp');
reset(fa);
assign(fb,'b.inp');
reset(fb);
assign(fc,'c.out');
rewrite(fc);

readln(fa,n);
readln(fb,m);
i:=1;
readln(fa,a);
j:=1;
readln(fb,b);
while (i<=n) or (j<=m) do begin
if (a<=b) then
begin
writeln(fc,a);
inc(i);
if (i<=n) then
readln(fa,a)

20
else
a:=maxlongint;
end else begin
writeln(fc,b);
inc(j);
if (j<=m) then
readln(fb,b)
else
b:=maxlongint;
end;
end;

close(fa);
close(fb);
close(fc);
end.

Bài 2:Hình chữ nhật


Cho N hình chữ nhật (2 <N< 500) có các cạnh song song với hai trục tọa độ và tọa
độ các đỉnh đều nguyên. Các hình chữ nhật được đánh số từ 1 đến N.
Yêu cầu: Hãy tìm hai hình chữ nhật mà phần giao nhau của chúng có diện tích lớn
nhất.
Dữ lieu cho trong file văn bản có tên là HCN.INP:
Dòng đầu chứa số N.
Dòng thứ I trong N dòng tiếp theo mô tả hình chữ nhật thứ I, chứa 4 số
nguyên x1, y1, x2, y2 ứng với các hòanh độ và tung độ của các hình chữ nhật (-
10000< x1, < x2 <10000; -10000< y1, < y2 <10000).
Kết quả xuất ra file văn bản HCN.OUT gồm 1 dòng duy nhất, chứa 2 số nguyên
dương cho biết chỉ số của 2 hình chữ nhật tìm được.

Ví dụ:
HCN . INP
HCN . OUT
3
1155 12
-5 -5 5 5
10 10 1000 1000

Lời giải:
Chúng ta cần viết thủ tục tính diện tích phần giao giữa hai hình chữ nhật. Chú ý phần
giao của hai hình chữ nhật cũng là một hình chữ nhật và có chiều dài/rộng bằng
phần giao của hai chiều dài/rộng tương ứng. Do đó ta chỉ cần viết thủ tục tìm phần
giao giữa hai đoạn thẳng trên trục số và sử dụng.
Thủ tục sau tìm giao giữa hai đoạn thẳng (a1,b1) và (a2,b2) trên trục số:
function giaodoan(a1,b1,a2,b2:longint):longint;
begin
if (b1<=a2) or (a1>=b2) then
giaodoan:=0 {hai đoạn không giao nhau}
else
giaodoan:=min(b1,b2)-max(a1,a2); {hai đoạn giao nhau}
end;

21
Thủ tục giaohcn sau áp dụng thủ tục giaodoan để tìm diện tích phần giao của hai
hình chữ nhật:
function giaohcn(i,j: longint): longint;
begin
{phần giao là một hình chữ nhật có chiều dài/rộng là phần giao của hai chiều
dài/rộng ban đầu}
giaohcn:=giaodoan(x1[i],x2[i],x1[j],x2[j])*giaodoan(y1[i],y2[i],y1[j],y2[j]);
end;

Sau khi có thủ tục tính diện tích phần giao giữa hai hình chữ nhật, ta xét qua tất cả
các cặp hình chữ nhật và chọn ra cặp có phần giao lớn nhất:
for i:=1 to n-1 do
for j:=i+1 to n do
begin
s:=giaohcn(i,j);
if s>kq then {tìm được hai hình có diện tích phần giao lớn hơn}
begin
kq:=s; {cập nhật kết quả}
luui:=i; {lưu lại chỉ số của hai hình}
luuj:=j;
end;
end;

Chương trình:
const MAXN=505;
finp='hcn.inp';
fout='hcn.out';

var
x1,y1,x2,y2: array[1..MAXN] of longint;

function min(a,b:longint):longint;begin if a<b then min:=a else min:=b; end;


function max(a,b:longint):longint;begin if a>b then max:=a else max:=b; end;

function giaodoan(a1,b1,a2,b2:longint):longint;
begin
if (b1<=a2) or (a1>=b2) then giaodoan:=0 else giaodoan:=min(b1,b2)-max(a1,a2);
end;

function giaohcn(i,j: longint): longint;


begin
giaohcn:=giaodoan(x1[i],x2[i],x1[j],x2[j])*giaodoan(y1[i],y2[i],y1[j],y2[j]);
end;

var n, kq, s, i, j, luui, luuj: longint;

begin
assign(input,finp);
reset(input);
assign(output,fout);

22
rewrite(output);

kq:=0;
readln(n);
for i:=1 to n do
readln(x1[i],y1[i],x2[i],y2[i]);
for i:=1 to n-1 do
for j:=i+1 to n do
begin
s:=giaohcn(i,j);
if s>kq then
begin
kq:=s;
luui:=i;
luuj:=j;
end;
end;
writeln(kq);
writeln(luui,' ',luuj);

close(input);
close(output);
end.

Bài 3: So sánh
Cho 2 số nguyên dương A,B (0<A, B< 10100 ).
Yêu cầu: hãy so sánh giá trị của 2 số.
Dữ liệu cho trong file văn bản có tện SO.INP gồm 2 dòng:
Dòng đầu chứa số A.
Dòng thứ 2 chứa số B.
Kết quả xuất ra file văn bản SO.OUT gồm 1 dòng duy nhất, chứa số -1,0 hoặc 1 lần
lượt tương ứng với các trường hợp sau: A < B, A = B, và A > B.
Ví dụ:
SO.INP SO.OUT
12345678900000001 1
12345678900000000

Lời giải:
Thủ tục so sánh hai số A, B có thể được xây dựng như sau:
 Xem A, B là xâu ký tự. Nếu độ dài của hai xâu khác nhau, ta thêm chữ số 0
vào đầu xâu ngắn hơn cho đến khi độ dài của hai xâu bằng nhau :
while (length(a)<length(b)) do a:='0'+a;
while (length(b)<length(a)) do b:='0'+b;

23
 Sau đó ta duyệt các chữ số từ trái sang phải, nếu chữ số tương ứng của A
bé hơn/lớn hơn của B thì kết luận A<B hay A>B và thoát khỏi thủ tục. Nếu
tất cả các cặp chữ số đều giống nhau thì ta kết luận A=B.
for i:=1 to length(a) do
if (a[i]<b[i]) then begin
sosanh:=-1; {kết luận A < B}
exit; {thoát khỏi thủ tục}
end else if (a[i]>b[i]) then
begin
sosanh:=1; {kết luận A > B}
exit; {thoát khỏi thủ tục}
end;
sosanh:=0; {kết luận A = B}

Chương trình:
const finp='so.inp';
fout='so.out';

function sosanh(a,b:string):longint;
var i: longint;
begin
while (length(a)<length(b)) do a:='0'+a;
while (length(b)<length(a)) do b:='0'+b;
for i:=1 to length(a) do
if (a[i]<b[i]) then begin
sosanh:=-1;
exit;
end else if (a[i]>b[i]) then
begin
sosanh:=1;
exit;
end;
sosanh:=0;
end;

var
a, b: string;
begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

readln(a);
readln(b);
writeln(sosanh(a,b));

close(input);
close(output);

24
end.

Bài 4: Bảng vuông


Cho một bảng vuông các số nguyên kích thước NxN (2 < N< 100) mà mỗi phần tử là
một số nguyên không âm và giá trị không vượt quá 100.
Yêu cầu: hãy tìm một bảng vuônmg con của bảng đã cho mà các phần tử của nó
chứa toàn số dương và tổng các phần tử thuộc bảng con này có giá trị lớn nhất.
Dữ liệu chop trong file văn bản có tên BANG.INP:
Dòng đầu chứa số N.
Dòng thứ I trong N dòng tiếp theo chứa N số nguyên dương ứng với dòng
thứ i của bảng.
Kết quả xuất ra file văn bản BANG.OUT chứa 1 số nguyên duy nhất chứa giá trọ
tổng lớn nhất tìm được.
Ví dụ:
BANG . INP BANG . OUT
3 6
110
121
112

Lời giải
Để giải quyết bài toán này, ta dùng một phương pháp thường gặp khi xử lý các phép
tính tổng trên bảng số: dùng bảng lưu tổng bộ phận. Cụ thể là, ta sẽ dùng bảng s với
ý nghĩa: s[i,j] là tổng các số trên bảng từ ô (1,1) đến ô (i,j). Mỗi khi đọc phần tử (i, j),
bảng s sẽ được cập nhật theo công thức
s[i,j]:=s[i-1,j]+s[i,j-1]-s[i-1,j-1]+a[i,j];
Và để tính tổng các số trong hình chữ nhật (i1,j1) đến (i2,j2) ta dùng công thức:
s[i2,j2]-s[i1-1,j2]-s[i2,j1-1]+s[i1-1,j1-1]
Để xử lý điều kiện bảng số chứa toàn số dương, ta dùng thêm bảng c theo kỹ thuật
tương tự bảng s với ý nghĩa: c[i,j] là số số 0 trên bảng ban đầu trong hình chữ nhật
từ ô (1,1) đến ô (i,j). Để xét xem một vùng hình chữ nhật có gồm toàn số dương hay
không, ta dùng công thức tính tổng như trên và xem kết quả có bằng 0 hay không.
Để duyệt qua các hình vuông con trên bảng số, ta xét tất cả các đỉnh trên trái (i,j) và
độ dài cạnh hình vuông L. Tổng các số trong hình vuông và điều kiện chứa toàn số
dương được tính theo công thức như trình bày ở trên. Thời gian thực hiện của thuật
toán là O(N3), hoàn toàn khả thi với giới hạn N ≤ 100.

Chương trình mẫu


const MAXN=105;
finp='bang.inp';
fout='bang.out';

type bang=array[0..MAXN, 0..MAXN] of longint;

25
function max(a,b:longint): longint;
begin
if a>b then max:=a else max:=b;
end;

function sum(a: bang; i1,j1,i2,j2: longint): longint;


begin
sum:=a[i2,j2]-a[i1-1,j2]-a[i2,j1-1]+a[i1-1,j1-1];
end;

var
c, s: bang;
n,i,j,l,x,i1,j1,i2,j2,kq: longint;

begin
assign(input,'bang.inp');
reset(input);
assign(output,'bang.out');
rewrite(output);

fillchar(s,sizeof(s),0);
fillchar(c,sizeof(c),0);
readln(n);
for i:=1 to n do
for j:=1 to n do begin
read(x);
s[i,j]:=s[i-1,j]+s[i,j-1]-s[i-1,j-1]+x;
c[i,j]:=c[i-1,j]+c[i,j-1]-c[i-1,j-1];
if (x=0) then inc(c[i,j]);
end;

kq:=0;
for i1:=1 to n do
for j1:=1 to n do
begin
l:=0;
repeat
i2:=i1+l;
j2:=j1+l;
if sum(c,i1,j1,i2,j2)=0 then
kq:=max(kq,sum(s,i1,j1,i2,j2))
else
break;
inc(l);
until (i2>n) or (j2>n);
end;
writeln(kq);

close(input);

26
close(output);
end.

Năm học 2006-2007


ĐOẠN CON DÀI NHẤT
(Bài 1 – Tuyển sinh 2006 - 2007)
Cho chuỗi kí tự S gồm toàn các chữ cái in hoa (A…Z) với độ dài không vượt quá
255. Hãy tìm đoạn con các kí tự liên tiếp dài nhất sao cho không có kí tự nào xuất
hiện nhiều hơn một lần. Trong trường hợp có nhiều hơn một đoạn con có cùng chiều
dài dài nhất, hãy chỉ ra đoạn xuất hiện đầu tiên trong chuỗi S.
Dữ liệu: Vào từ văn bản SUBSTR.INP gồm một dòng duy nhất chứa chuỗi S.
Kết quả: Ghi ra file văn bản SUBSTR.OUT hai số nguyên P và L tương ứng là vị trí
và chiều dài của đoạn con dài nhất tìm được (kí tự đầu tiên trong chuỗi có vị trí là 1).
Ví dụ:
SUBSTR.INP SUBSTR.OUT
ABABCDAC 3 4

Lời giải
Ta duyệt qua lần lượt các ký tự của chuỗi. Khi duyệt qua ký tự thứ i, ta sẽ tìm cách
xây dựng chuỗi dài nhất không có ký tự nào xuất hiện hai lần và kết thúc tại vị
trí i. Để xây dựng được chuỗi này, ta cần lưu trữ các thông tin sau đây:
Vị trí bắt đầu của chuỗi dài nhất mà không có ký tự nào xuất hiện hai lần, ta
ký hiệu là p.
 Mảng b['A'..'Z'] trong đó với ký tự c thì b[c] lưu vị trí xuất hiện cuối cùng của
ký tự c.
Khi duyệt qua ký tự thứ i (ký hiệu là s[i]):
 Nếu vị trí xuất hiện cuối cùng của ký tự này không nhỏ hơn p, nghĩa là ký tự
này sẽ xuất hiện 2 lần và làm cho chuỗi đang xét trở nên không hợp lệ. Khi
đó ta cập nhật lại vị trí bắt đầu p bằng b[s[i]]+1.
 Ta cập nhật lại giá trị i cho b[s[i]] để đảm bảo ý nghĩa của mảng b
 Chuỗi đang xét sẽ bắt đầu từ vị trí p và kết thúc tại vị trí i, do đó chuỗi này có
độ dài là i-p+1, ta dùng giá trị này so sánh với kết quả để tìm ra chuỗi dài
nhất.
Đoạn chương trình sau đây thể hiện thuật toán:
p:=1; {gán giá trị khởi tạo cho p: vị trí bắt đầu là 1}
for i:=1 to length(s) do {duyệt qua từng ký tự của chuỗi}
begin
if (b[s[i]]>=p) then {ký tự s[i] xuất hiện hai lần trong chuỗi đang xét!}
p:=b[s[i]]+1; {cập nhật lại vị trí bắt đầu mới}
b[s[i]]:=i; {cập nhật lại vị trí xuất hiện của ký tự s[i]}
if (i-p+1>l) then begin {i-p+1 là độ dài của chuỗi đang xét}
{so sánh với độ dài lớn nhất l}
luup:=p; {lưu lại vị trí bắt đầu}
l:=i-p+1; {cập nhật lại độ dài lớn nhất}

27
end;
end;

Chương trình:
const finp='substr.inp';
fout='substr.out';
var
b:array['A'..'Z'] of longint;
s:string;
p,i,l,luup: longint;
begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);
readln(s);
fillchar(b,sizeof(b),0);
p:=1;
for i:=1 to length(s) do
begin
if (b[s[i]]>=p) then
p:=b[s[i]]+1;
b[s[i]]:=i;
if (i-p+1>l) then begin
luup:=p;
l:=i-p+1;
end;
end;
writeln(luup,' ',l);
close(input);
close(output);
end.

ĐƯỜNG ĐI
(Bài 2 – Tuyển sinh 2006 - 2007)
Một con robot di chuyển theo một chương trình định sẵn trên mặt phẳng toạ độ.
Chương trình này được thể hiện dưới dạng một dãy N lệnh (1  N  3000). Các lệnh
thuộc một trong các dạng sau:
 F S: Đi thẳng theo hướng hiện tại S bước.
 R S: Rẽ phải 900 và đi S bước.
 L S: Rẽ trái 900 và đi S bước.
Yêu cầu: Cho một chương trình điều khiển robot, hãy xác định chiều dài T đoạn
đường mà con robot đã đi được, biết mỗi bước của nó dài d(cm). Ban đầu con robot
đứng tại vị trí (0,0) và hướng theo chiều dương của trục hoành.
Dữ liệu: Vào từ file văn bản PATH.INP:
 Dòng đầu tiên chứa 2 số nguyên dương N và d.
 N dòng tiếp theo, mỗi dòng chứa một lệnh theo quy cách nêu trên.

28
Kết quả: Ghi ra file PATH.OUT chứa chiều dài T tìm được.
Ví dụ:
PATH.INP PATH.OUT
4 1 23
F 5
R 7
F 2
L 9

Lời giải:
Kết quả của bài toán này bằng tích của d và tổng của độ dài các bước đi. Các lệnh
F, R, L không ảnh hưởng đến kết quả của bài toán và chỉ được đưa ra để kiểm tra
thí sinh có thuần thục trong việc đọc dữ liệu hay không.
Mỗi câu lệnh sẽ gồm hai ký tự (một chữ cái chỉ lệnh và một khoảng trắng) và một số,
do đó ta đọc vào hai biến kiểu ký tự và một biến kiểu số nguyên:
readln(c,c,x);

Ta sẽ sử dụng biến x, còn c chỉ là biến tạm không được dùng đến.

Chương trình mẫu


const finp='path.inp';
fout='path.out';

var i, n, d, x, s: longint;
c: char;
begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);
readln(n,d);
for i:=1 to n do begin
readln(c,c,x);
s:=s+x;
end;
writeln(x);
close(input);
close(output);
end.

ĐẾM VÙNG
(Bài 3 – Tuyển sinh 2006 - 2007)
Một khu vườn hình chữ nhật được chia thành M  N ô đơn vị. Các dòng đánh số
từ 1 tới M từ trên xuống dưới, các cột đánh số từ 1 đến N từ trái sang phải. Ô nằm ở
hàng i, cột j được gọi là ô (i,j). Người ta có đắp K lối đi trên mảnh vườn đó, lối đi thứ
i là một dãy các ô liên tiếp nhau theo đường ngang hoặc đường dọc, và được cho

29
bởi 4 số nguyên dương xi, yi, zi và ti trong đó (xi, yi) là vị trí của ô đầu, còn (zi, ti) là vị
trí của ô cuối của lối đi. Các lối đi chia khu vườn thành các miền. Mỗi miền là một tập
tất cả các ô không thuộc các lối đi sao cho hai ô bất kì trong đó có thể đi tới bằng
cách di chuyển qua các ô chung cạnh và không phải là ô thuộc lối đi.
Yêu cầu: Hãy xác định số miền S mà các lối đi chia khu vườn.
Dữ liệu: Vào từ file văn bản REGIONS.INP trong đó:
 Dòng đầu chứa 3 số M, N, K.
 Dòng thứ i trong K dòng tiếp theo chứa 4 số xác định lối đi thứ i: xi, yi, zi, ti.
Kết quả: Ghi ra file văn bản REGIONS.OUT số S tìm được.
Ví dụ:
REGIONS.INP REGIONS.OUT
10 10 2 3
5 1 5 10
1 5 7 5

SỐ ƯỚC
(Bài 4 – Tuyển sinh 2006 - 2007)
Cho số nguyên dương N. Giai thừa của N, kí hiệu là N!, là tích của các số tự nhiên
từ 1 đến N. Gọi T là số lượng ước lớn hơn 1 của N!. Ví dụ với N = 4, ta có 4! = 24.
Như vậy 4! có 7 ước lớn hơn 1 là: 2, 3, 4, 6, 8, 12, 24.
Yêu cầu: Cho N, hãy xác định T.
Dữ liệu: Vào từ file văn bản DIVISORS.INP trong đó chứa duy nhất số N (N  20,
trong đó 50% số test có N  10).
Kết quả: Ghi ra file văn bản DIVISORS.OUT số T tìm được.
Ví dụ:
DIVISORS.INP DIVISORS.OUT
4 7
Lời Giải
Nếu một số nguyên m được phân tích ra thừa số nguyên tố dưới dạng
m=p1a1p2a2...pkak thì số ước số của m bằng tích (a1+1)(a2+1)...(ak+1). Ta sẽ tìm cách
phân tích n! ra thừa số nguyên tố. Các ước số nguyên tố của n! chỉ nằm trong phạm
vi từ 2 đến n, do đó ta duyệt qua tất cả các số nguyên tố trong phạm vi này. Với số
nguyên tố p, số mũ của nó trong biểu diễn nguyên tố của n! bằng:
[n/p] + [n/p2] + [n/p3] + ...
Trong đó [n/p] ký hiệu phần nguyên của n/p, nói cách khác trong ngôn ngữ Pascal
[n/p] bằng n div p. Công thức trên được giải thích như sau: [n/p] là số lượng số từ 1
đến n chia hết cho p, [n/p2] là số lượng số từ 1 đến n chia hết cho p 2,... mỗi số này
đều đóng góp một thừa số nguyên tố p phân biệt cho n!.
Đoạn chương trình sau đây thể hiện thuật toán:
for i:=2 to n do if nguyento(i) then {duyệt qua các số nguyên tố từ 2 đến n}

30
begin
a:=0;
j:=i; {biến j sẽ duyệt qua các số mũ của i là i, i2, i3,...}
while (j<=n) do {đến khi j>n thì dừng lại vì khi đó [n/j] sẽ luôn bằng 0}
begin
a:=a+n div j;
j:=j*i;
end;
{đến đây a=[n/i]+[n/i2]+... chính là số thừa số i
trong biểu diễn nguyên tố của n!}
d:=d*(a+1); {nhân thêm (a+1) vào số ước, theo công thức tính số ước nêu trên}
end;

Chương trình:
function nguyento(n: longint): boolean;
var i: longint;
begin
nguyento:=false;
for i:=2 to trunc(sqrt(n)) do
if n mod i = 0 then exit;
nguyento:=true;
end;

var
n,i,j,a,d: longint;

begin
readln(n);
d:=1;
for i:=2 to n do if nguyento(i) then
begin
a:=0;
j:=i;
while (j<=n) do
begin
a:=a+n div j;
j:=j*i;
end;
d:=d*(a+1);
end;
writeln(d-1);
end.

Năm học 2007-2008


TÍCH LỚN NHẤT
(Bài 1 – Tuyển sinh 2007 - 2008)
Cho một dãy gồm N số nguyên. Hãy tìm 3 số trong dãy với tích T của chúng là lớn
nhất.

31
Dữ liệu: Vào từ file văn bản TICHMAX.INP:
 Dòng đầu ghi số N (3  N  10000).
 Dòng thứ hai chứa N số nguyên có giá trị tuyệt đối không vượt quá 30000.
Kết quả: Ghi ra file văn bản TICHMAX.OUT một số duy nhất T.
Ví dụ:
TICHMAX.INP TICHMAX.OUT
9 810
3 5 1 7 9 0 9 -3 10

Lời giải
Nếu tích lớn nhất của 3 phần tử bao gồm:
 Ba số dương: ba số này phải lần lượt là số lớn nhất, nhì, ba trong dãy
 Một số âm và hai số dương: dãy phải gồm đúng hai số dương, vì nếu không
ta có thể lấy tích ba số dương để đạt giá trị lớn hơn. Ta cũng suy ra ba số
này phải là ba số lớn nhất, nhì, ba trong dãy.
 Hai số âm và một số dương: số dương phải là số lớn nhất và hai số âm phải
là hai số nhỏ nhất, nhì trong dãy.
 Ba số âm: dãy phải gồm toàn số âm, vì nếu có một số dương ta cũng có thể
lấy tích của số dương đó và hai số âm để thu được tích dương. Ta cũng suy
ra được ba số cần tìm phải là ba số lớn nhất, nhì, ba của dãy.
Vậy suy ra, T=max(max1*max2*max3,min1*min2*max1), với max1, max2, max3,
min1, min2 lần lượt là số lớn nhất, nhì, ba và số nhỏ nhất, nhì của dãy.
Ta có thể đọc qua lần lượt các số của dãy và cập nhật max1, max2, max3, min1,
min2 mà không cần lưu lại dãy số. Đoạn lệnh dưới đây cập nhật min1, min2 khi đọc
vào một số mới x:
if (x<=min1) then
begin
min2:=min1; {min1 giờ trở thành số bé thứ hai}
min1:=x; {số bé nhất là x}
end else if (x<=min2) then
min2:=x; {số bé thứ hai là x}

Việc cập nhật max1, max2, max3 được thực hiện hoàn toàn tương tự.
Chú ý vì kết quả có thể vượt quá kiểu số nguyên 32 bit nên ta cần khai báo với kiểu
số nguyên 64 bit (trình biên dịch Free Pascal hỗ trợ kiểu số nguyên 64 bit với tên gọi
int64):
var
...
kq:int64;
...
kq:=max(max1*max2*max3,min1*min2*max1);

Chương trình:
const finp='tichmax.inp';
fout='tichmax.out';

32
var max1,max2,max3,min1,min2,x,i,n:longint;
kq:int64;

function max(a,b:longint):longint;
begin
if a>b then max:=a else max:=b;
end;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

max1:=-maxlongint;
max2:=max1;
max3:=max1;
readln(n);
for i:=1 to n do begin
read(x);
if (x>=max1) then
begin
max3:=max2;
max2:=max1;
max1:=x;
end else if (x>=max2) then
begin
max3:=max2;
max2:=x;
end else if (x>=max3) then
begin
max3:=x;
end;
if (x<=min1) then
begin
min2:=min1;
min1:=x;
end else if (x<=min2) then
min2:=x;
end;
kq:=max(max1*max2*max3,min1*min2*max1);

writeln(kq);

close(input);
close(output);
end.

33
TÌM DÃY K
(Bài 2 – Tuyển sinh 2007 - 2008)
Cho một xâu S có độ dài N. Với mỗi số nguyên i (1  i  N), xét xâu con Si gồm i kí
tự đầu của xâu S. Người ta cần xác định giá trị K i cho mỗi xâu Si là giá trị lớn nhất
thỏa mãn điều kiện sau:
Nếu xâu X là xâu con gồm Ki kí tự đầu của xâu Si và tạo xâu Y bằng
cách viết Ki kí tự cuối cùng của xâu S i theo thứ tự ngược từ cuối lên
đầu ta có X = Y.
Ví dụ, nếu S = ‘acbaca’ thì S4 = ‘acba’ và vì vậy K4 = 1, vì khi đó X = Y = ‘a’.
S6 = ‘acbaca’ và vì vậy K6 = 2 do khi đó X = Y = ‘ac’.

Yêu cầu: Cho xâu S độ dài N, tìm các số K1, K2, …, KN.
Dữ liệu: Vào từ file văn bản DAYK.INP:
 Dòng đầu tiên chứa số nguyên dương N (N  255).
 Dòng thứ hai chứa xâu S độ dài N.
Kết quả: Ghi ra file văn bản DAYK.OUT trên một dòng duy nhất dãy số K1, K2, …, KN.
Ví dụ:
DAYK.INP DAYK.OUT
6 1 0 0 1 0 2
acbaca

Lời giải
Ta dùng vòng lặp i từ 1 đến N, với mỗi giá trị i ta tìm cách xác định số K i theo như
yêu cầu của đề bài. Số Ki chính là phần dài nhất mà khi nhìn từ trái sang phải và từ
phải sang trái của chuỗi Si đều như nhau, nói cách khác trên chuỗi S i ký tự đầu tiên
bằng ký tự cuối, ký tự thứ hai bằng ký tự kề cuối, ..., ký tự thứ Ki bằng ký tự thứ i-Ki.
Để xác định Ki ta dùng vòng lặp while tăng dần giá trị của Ki khi nào điều kiện
s[1+k]=s[i-k] còn đúng:
k:=0;
while (s[1+k]=s[i-k]) do inc(k);

Chương trình:
const finp='dayk.inp';
fout='dayk.out';

var n, i, k: longint;
s: string;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

34
readln(n);
readln(s);
for i:=1 to n do
begin
k:=0;
while (s[1+k]=s[i-k]) do inc(k);
write(k,' ');
end;

close(input);
close(output);
end.

TÌM SỐ ÂM LỚN NHẤT


(Bài 3 – Tuyển sinh 2007 - 2008)
Cho một dãy gồm N số nguyên a1, a2, …, aN, mỗi số có giá trị tuyệt đối không vượt
quá 105.
Yêu cầu: Hãy tìm số âm lớn nhất X trong dãy.
Dữ liệu: Vào từ file văn bản SOAM.INP:
 Dòng đầu tiên chứa số nguyên dương N (1  N  105).
 N dòng tiếp theo, dòng thứ i chứa số ai.
Kết quả: Ghi ra file văn bản SOAM.OUT trên một dòng duy nhất số X tìm được.
Trong trường hợp không có lời giải, ghi ra số 0.
Ví dụ:
SOAM.INP SOAM.OUT
5 -4
-4
3
2
-5
7

Lời giải:
Ta đọc qua các số của dãy và tìm số lớn nhất, tuy nhiên chỉ các số âm mới được xét
đến. Ta dùng thêm một cờ để kiểm tra trong dãy có tồn tại số âm hay không.
if (x<0) then {chỉ xét số âm}
begin
co:=true; {đánh dấu cờ là có tồn tại số âm}
if (x>kq) then kq:=x; {cập nhật với kết quả lớn nhất}
end;

Chương trình:
const finp='soam.inp';

35
fout='soam.out';

var n, kq, i, x: longint;


co: boolean;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

co:=false;
readln(n);
kq:=-maxlongint;
for i:=1 to n do
begin
readln(x);
if (x<0) then
begin
co:=true;
if (x>kq) then kq:=x;
end;
end;
if co then writeln(kq) else writeln(0);
end.

DÃY HÌNH CHỮ NHẬT LỒNG NHAU


(Bài 4 – Tuyển sinh 2007 - 2008)
Trên mặt phẳng tọa độ cho N hình chữ nhật với các cạnh song song với hệ trục tọa
độ, các hình chữ nhật được đánh số từ 1 tới N. Hình chữ nhật thứ i được cho bởi 4
số nguyên dương xi1, yi1, xi2, yi2, trong đó (xi1, yi1) là tọa độ đỉnh trái dưới, còn (xi2, yi2)
là tọa độ đỉnh phải trên. Ta nói rằng hình chữ nhật thứ I nằm trong hình chữ nhật thứ
j nếu trên mặt phẳng tọa độ, mọi điểm của hình chữ nhật i đều thuộc hình chữ nhật j.
Yêu cầu: Với N hình chữ nhật cho trước, hãy tìm K hình chữ nhật với chỉ số i 1, i2, …,
iK sao cho hình i1 nằm trong hình i2, hình i2 nằm trong hình i3, …, hình iK-1 nằm trong
hình iK và K là lớn nhất. Biết rằng hai hình chữ nhật bất kì trong N hình chữ nhật đã
cho hoặc rời nhau hoặc một trong hai hình nằm trong hình còn lại.
Dữ liệu: Vào từ file văn bản HCN.INP:
 Dòng đầu tiên chứa số nguyên dương N (1  N  100).
 N dòng tiếp theo, dòng thứ i chứa 4 số nguyên dương xi1, yi1, xi2, yi2.
Kết quả: Ghi ra file văn bản HCN.OUT số K tìm được.
Ví dụ:
HCN.INP HCN.OUT
3 2
1 1 7 4

36
3 1 6 6
2 2 5 4

Lời giải:
Bước 1, ta sắp xếp các hình chữ nhật theo thứ tự giảm dần của diện tích. Điều này
đảm bảo một hình chữ nhật chỉ có thể bị bao bởi một hình chữ nhật xuất hiện phía
trước nó. Đoạn chương trình sau thực hiện việc sắp xếp:
for i:=1 to n-1 do
for j:=i+1 to n do
if (dientich(h[i])<dientich(h[j])) then
begin
tam:=h[i];h[i]:=h[j];h[j]:=tam;
end;

Giả sử các hình chữ nhật đã được sắp xếp là h1, h2, ..., hn. Ta gọi f[i] là độ dài lớn
nhất của chuỗi hình chữ nhật lồng nhau kết thúc tại hình h i. Để tính f[i], ta tìm vị trí j
lớn nhất sao cho j < i là hình chữ nhật h j bao hình chữ nhật hi, khi đó f[i]=f[j]+1. Nếu
không tồn tại vị trí j thì hình chữ nhật hi không bị bao bởi hình chữ nhật nào và f[i]=1:
f[i]:=1; {khởi tạo giá trị ban đầu cho f[i]}
for j:=i-1 downto 1 do
if bao(h[j],h[i]) then {j là vị trí lớn nhất mà hình chữ nhật
h[j] bao hình chữ nhật h[i]}
begin
f[i]:=f[j]+1; {tính f[i] theo f[j]}
break; {thoát khỏi vòng lặp}
end;
if (f[i]>kq) then kq:=f[i]; {cập nhật lại kết qủa}

Chương trình:
const
finp='hcn.inp';
fout='hcn.out';
MAXN=105;

type hcn=record x1,y1,x2,y2: longint; end;

var
h: array[1..MAXN] of hcn;
f: array[1..MAXN] of longint;
i,j,n,kq: longint;
tam:hcn;

function dientich(a:hcn):longint;
begin
with a do dientich:=(x2-x1)*(y2-y1);
end;

function bao(a,b:hcn):boolean;
begin

37
bao:=(a.x1<=b.x1) and (b.x2<=a.x2) and
(a.y1<=b.y1) and (b.y2<=a.y2);
end;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

readln(n);
for i:=1 to n do
with h[i] do
readln(x1,y1,x2,y2);
for i:=1 to n-1 do
for j:=i+1 to n do
if (dientich(h[i])<dientich(h[j])) then
begin
tam:=h[i];h[i]:=h[j];h[j]:=tam;
end;
kq:=0;
for i:=1 to n do
begin
f[i]:=1;
for j:=i-1 downto 1 do
if bao(h[j],h[i]) then
begin
f[i]:=f[j]+1;
break;
end;
if (f[i]>kq) then kq:=f[i];
end;
writeln(kq);

close(input);
close(output);
end.

Năm học 2008-2009


Bài 1: Mã hoá
Để quản lý tốt các hồ sơ trong kỳ thi tuyển sinh, hội đồng tuyển sinh trường PTNK đã
quyết định đánh số các hồ sơ theo một phương pháp khoa học. Mã hồ sơ của thí
sinh là một chuỗi gồm 10 chữ số. Tuy nhiên không phải bất kỳ chuỗi 10 chữ số nào
cũng là mã hồ sơ hợp lệ bởi vì hội đồng tuyển sinh đưa ra một quy định ràng buộc
chặt chẽ cho các chữ số đó. Nếu M=a1a2..a10 là một mã hồ sơ thì M phải thỏa mãn
ràng buộc:
Nếu đặt S(M)=1a1+2a2+3a3+…+10a10 thì S(M) phải là một số chia hết cho 11.

38
Nhờ quy định này, trong những trường hợp do sơ xuất có một chữ số trong mã hồ
sơ bị mờ, không đọc được thì ta vẫn có thể xác định được giá trị của nó. Ví dụ như:
(quy ước ? là chữ số bị mờ):
 Với M=00000000?1 thì có thể suy ra chữ số bị mờ là 5 vì theo ràng buộc, để
S(M) là một số chia hết cho 11 nó chỉ có thể có giá trị là 55.
 Tương tự với M=00000001?1 thì có thể suy ra chữ số bị mờ là 9.
 Tương tự với M=00722?0858 thì có thể suy ra chữ số bị mờ là 6.
Yêu cầu: Hãy viết chương trình giúp hội đồng tuyển sinh suy ra được chữ số bị mờ
trong mã hồ sơ.
Dữ liệu: Vào từ file văn bản ENCODE.INP có chứa mã hồ sơ có 1 chữ số bị mờ
được thay bằng dấu chấm hỏi.
Kết quả: Ghi ra file văn bản ENCODE.OUT chứa giá trị của chữ số bị mờ trong mã
hồ sơ đã cho.
Ví dụ:
ENCODE.INP
00000000?1
ENCODE.OUT
5
00000001?1
9
00722?0858
6
Lời giải:
Ta thử thay từng chữ số từ 0 đến 9 vào vị trí "?" và tính xem điều kiện S(M) chia hết
cho 11 có được thoả mãn hay không như đoạn chương trình sau đây:
for i:=1 to 10 do
{r là tổng các số hạng của S(M) trừ vị trí có dấu ‘?’}
if (s[i]<>'?') then r:=(r+i*(ord(s[i])-ord('0'))) mod 11;
i:=pos('?',s); {i là vị trí của dấu ‘?’}
for d:=0 to 9 do {duyệt qua tất cả các chữ số từ 0 đến 9}
if (r+i*d) mod 11 = 0 then {kiểm tra xem S(M) có chia hết cho 11}
begin
writeln(d); {d chính là chữ số cần tìm}
break; {thoát khỏi vòng lặp}
end;

Chương trình:
const
finp='encode.inp';
fout='encode.out';

var
s: string;
i, r, d: longint;
c: char;

39
begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

readln(s);
for i:=1 to 10 do
if (s[i]<>'?') then r:=(r+i*(ord(s[i])-ord('0'))) mod 11;
i:=pos('?',s);
for d:=0 to 9 do
if (r+i*d) mod 11 = 0 then
begin
writeln(d);
break;
end;
close(input);
close(output);
end.

Bài 2: Biến đổi dãy số


Cho dãy các số nguyên khác nhau đôi một a gồm n số a1, a2, ..., an và dãy số nguyên
b gồm n số b1, b2, ..., bn. Trên dãy số a ta có thể áp dụng phép biến đổi T(i) thực hiện
phép hoán vị giá trị hai phần tử ai và ai+1 (0<i<n) .Vấn đề đặt ra là có tồn tại hay
không một dãy các phép biến đổi Ti1, Ti2, …, Tik sao cho khi áp dụng, dãy a ban đầu
sẽ biến thành dãy b.
Ví dụ nếu dãy a là 1, 3, 2, 4 và dãy b là 2, 1, 3, 4 thì ta có thẻ sử dụng dãy 2 phép
biến đổi T(2) và T(1) để biến đổi a thành b.
Yêu cầu: Cho hai dãy số a, b . Hãy chỉ ra một dãy các phép biến đổi a thành b hoặc
cho biết không tồn tại dãy biến đổi như vậy.
Dữ liệu: Vào từ file văn bản EXARRAY.INP gồm 3 dòng:
 Dòng đầu tiên chứa số nguyên n là số lượng phần tử của mỗi dãy số
(n<=100).
 Dòng thứ hai chứa n số nguyên đôi một khác nhau ứng với các phần tử của
dãy số a.
 Dòng cuối cùng chứa n số nguyên ứng với các phần tử của dãy số b.
Các số trong 2 mảng a và b đều có giá trị nằm trong đoạn [-10000,10000].
Kết quả: ghi ra file văn bản EXARRAY.OUT
 Nếu không thể biến đổi a thành b, file chứa số duy nhất số -1.
 Trong trường hợp ngựoc lại, file sẽ gồm 2 dòng:
o Dòng đầu tiên chứa số nguyên k là số lượng phép biến đổi cần áp
dụng.
o Dòng thứ hai chứa k số nguyên duơng i1, i2, …, ik ứng với các phép
biến đổi Ti1, Ti2, …, Tik tìm được.
Hai số liên tiếp trên cùng một dòng được ghi cách nhau bởi một dấu cách.
Ví dụ:

40
EXARRAY.INP
4
1324
2134

EXARRAY.OUT
2
21

EXARRAY.INP
1
5
2
EXARRAY.OUT
-1

Lời giải:
Thuật toán của ta đơn giản như sau: tìm b[1] trong dãy a rồi di chuyển lên đầu dãy a,
sau đó lại tìm b[2] rồi di chuyển lên vị trí thứ hai của dãy a, ... Nếu tại một bước ta
không tìm thấy số tương ứng trong dãy a thì in ra -1.
Đoạn chương trình sau thể hiện thuật toán:
for i:=1 to n do {duyệt qua tất cả các phần tử của dãy b}
begin
vt[i]:=0; {vt[i] là vị trí xuất hiện của b[i] trong dãy a}
for j:=i to n do {duyệt qua các phần tử của dãy a để tìm số b[i]}
if (a[j]=b[i]) then {tìm đặt b[i]}
begin
vt[i]:=j; {cập nhật lại vt[i]}
break; {thoát khỏi vòng lặp}
end;
if (vt[i]<>0) then {nếu tìm được số b[i]}
begin
kq:=kq+vt[i]-i; {cần thực hiện thêm vt[i]-i phép biển đổi
để đưa số từ vị trí vt[i] về vị trí i}
for j:=vt[i]-1 downto i do trao(j) {thực hiện biến đổi!}
end
else
begin {nếu không tìm được số b[i]}
writeln(-1); {in ra -1}
exit; {thoát khỏi thủ tục}
end;
end;

Chương trình:

41
const MAXN=102;
finp='exarray.inp';
fout='exarray.out';

var n: longint;
a,b:array[1..MAXN] of longint;

procedure nhap;
var i: longint;
begin
readln(n);
for i:=1 to n do read(a[i]);
for i:=1 to n do read(b[i]);
end;

var kq: longint;


vt: array[1..MAXN] of longint;

procedure trao(i: longint);


var tam: longint;
begin
tam:=a[i]; a[i]:=a[i+1]; a[i+1]:=tam;
end;

procedure xuly;
var i, j, tam: longint;
begin
for i:=1 to n do
begin
vt[i]:=0;
for j:=i to n do
if (a[j]=b[i]) then
begin
vt[i]:=j;
break;
end;
if (vt[i]<>0) then
begin
kq:=kq+vt[i]-i;
for j:=vt[i]-1 downto i do trao(j)
end
else
begin
writeln(-1);
exit;
end;
end;
writeln(kq);
for i:=1 to n do
for j:=vt[i]-1 downto i do

42
write(j,' ');
end;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

nhap;
xuly;

close(input);
close(output);
end.

Bài 3: Biến đổi bảng


Xét bảng vuông gồm n dòng và n cột. Các dòng được đánh số từ 1 đến n từ trên
xuống dưới. Các cột được đánh số từ 1 đến n từ trái sang phải. Ô nằm ở vị trí dòng i
và cột j của bảng được gọi là ô (i,j). Trên bảng A đã cho, khoảng cách từ ô (i,j) đến ô
(p,q) được tính bằng |i-p|+|j-q|. Tại ô (i,j) của bảng A ghi số nguyên không âm a ij,
i=1,2,…,n; j=1,2,..,n. Dựa vào các số được ghi trên bảng A, người ta cần xây dựng
một bảng B cùng kích thước với A mà trên đó ô (i,j) của bảng B sẽ được ghi số b ij
xác định như sau:
 Nếu aij > 0 thì bij = aij
 Nếu aij = 0 thì bij có giá trị bằng giá trị a pq của ô (p,q) gần ô (i,j) nhất trong số
các ô có giá trị khác không trên dòng i và cột j của bảng A. Trong truờng hợp
có nhiều ô khác không có cùng khoảng cách nhỏ nhất đến (i,j) thì ô (p,q)
đựoc chọn là ô chứa số lớn nhất trong chúng. Nếu tất cả các phần tử của
dòng i và cột j đều có giá trị 0 thì bij = 0.
Yêu cầu: cho bảng A, hãy tìm bảng B.
Dữ liệu: vào từ file văn bản NZTABLE.INP gồm:
 Dòng đầu tiên ghi số nguyên dương n (n ≤ 50)
 Dòng thứ i trong số n dòng tiếp theo ghi n số nguyên không âm ai1, ai2, …, ain
là các số trên dòng thứ i của bảng, i=1,2,…,n; aij ≤ 10000.
Kết quả: đưa ra file văn bản NZTABLE.OUT gồm n dòng, dòng thứ i ghi n số nguyên
dương bi1, bi2, …, bin là các số trên dòng thứ i của bảng B.
Hai số liên tiếp trên cùng một dòng được ghi cách nhau bởi một dấu cách.
Ví dụ:
NZTABLE.INP
4
1030
4005
0060
0000

43
NZTABLE.OUT
1335
4465
4666
4065

Lời giải:
Ta xây dựng mảng b giống như định nghĩa của đề bài như đoạn chương trình sau:
if (a[i,j]>0) then b:=a[i,j] else {biển b chỉ phần tử bij. Nếu aij > 0 thì bij = aij}
begin
b:=-maxlongint; {gán bij=-∞ vì ta cần giá trị bij lớn nhất}
kc:=maxlongint; {kc là biến lưu khoảng cách, gán bằng ∞
vì ta cần giá trị nhỏ nhất}
for p:=1 to n do {duyệt qua các ô trên cột j}
if (a[p,j]<>0) then {chỉ xét các ô có giá trị khác 0}
if ((abs(i-p)<kc) or {nếu ô có khoảng cách gần hơn}
((abs(i-p)=kc) and (a[p,j]>b))) {có khoảng cách bằng nhưng giá trị lớn hơn}
then
begin
kc:=abs(i-p); {cập nhật lại khoảng cách}
b:=a[p,j]; {cập nhật lại bij}
end;
for p:=1 to n do {duyệt qua các ô trên dòng i}
... {thực hiện tương tự}
if (kc=maxlongint) then b:=0; {nếu tất cả các phần tử của dòng i, cột j
đều bằng 0 thì bij=0}
end;
write(b,' '); {in bij ra màn hình}

Chương trình:
const MAXN=55;
finp='NZTABLE.INP';
fout='NZTABLE.OUT';

var i, j, b, n, kc, p: longint;


a: array[1..MAXN, 1..MAXN] of longint;

begin
assign(input, finp);
reset(input);
assign(output, fout);
rewrite(output);

readln(n);
for i:=1 to n do
for j:=1 to n do
read(a[i,j]);

44
for i:=1 to n do
begin
for j:=1 to n do
begin
if (a[i,j]>0) then b:=a[i,j] else
begin
b:=-maxlongint;
kc:=maxlongint;
for p:=1 to n do
if (a[p,j]<>0) then
if ((abs(i-p)<kc) or ((abs(i-p)=kc) and (a[p,j]>b))) then
begin
kc:=abs(i-p);
b:=a[p,j];
end;
for p:=1 to n do
if (a[i,p]<>0) then
if ((abs(j-p)<kc) or ((abs(j-p)=kc) and (a[i,p]>b))) then
begin
kc:=abs(j-p);
b:=a[i,p];
end;
if (kc=maxlongint) then b:=0;
end;
write(b,' ');
end;
writeln;
end;

close(input);
close(output);
end.

Bài 4: Tìm mật khẩu


Việc bảo vệ máy tính của mình để hạn chế người khác thâm nhập vào là một vấn đề
đặt ra cho mọi nguời sử dụng máy tính. Để tăng tính an toàn trong lưu trữ, một nguời
đã quyết định dấu mật khẩu truy cập máy tính của mình vào một xâu T với một quy
ước sao cho khi cần anh ta có thể lấy lại đuợc mật khẩu từ T như sau.
Là một người yêu thích số học anh ta thường chọn mật khẩu P là một số nguyên tố
và đem dấu vào một xâu ký tự T sao cho P chính là số nguyên tố có giá trị lớn nhất
trong số các số nguyên tố tạo được từ các xâu con của T (xâu con của một xâu ký tự
T là một chuỗi liên tiếp các ký tự trong T).
Ví dụ: xâu T=”Test1234#password5426” chứa mật khẩu là 23 vì T chứa các xâu con
ứng với các số nguyên tố 2,3,23 và 5.
Yêu cầu: Cho một xâu ký tự T chiều dài không quá 250 ký tự. Tìm mật khẩu P đã
dấu trong xâu T biết P có giá trị nhỏ hơn 105. Dữ liệu cho đảm bảo T chứa ít nhất 1
số nguyên tố.
Dữ liệu: Vào từ file văn bản PASSWORD.INP gồm 1 dòng duy nhất là xâu T.

45
Kết quả: Ghi ra file văn bản PASSWORD.OUT chứa số P tìm được.
Ví dụ:
PASSWORD.INP
Test1234#password5426
PASSWORD.OUT
23

Lời giải:
Ta duyệt qua tất cả các xâu con của xâu T mà có thể tạo thành một số và kiểm tra số
đó có phải số nguyên tố hay không.
Để duyệt qua các xâu con, ta duyệt qua vị trí đầu:
for i:=1 to length(t) do {i là vị trí đầu của xâu con}

Với mỗi vị trí đầu i, ta duyệt qua vị trí cuối của xâu con:
j:=i;
while (j<=length(t)) do
begin
...
inc(j);
end;

Có hai điều kiện để ta dừng quá trình duyệt một xâu con với vị trí đầu là i:
 Gặp một ký tự không phải chữ số:
if (t[j]<'1') or (t[j]>'9') then break;

 Số tạo thành lớn hơn hoặc bằng 105, vì đề bài đã nêu rõ P có giá trị nhỏ hơn
105:
if v>=100000 then break;

Khi đọc được một chữ số mới, ta nhân số hiện tại với 10 rồi cộng thêm chữ số mới:
v:=v*10+ord(t[j])-ord('0');

Nếu số thu được là số nguyên tố và lớn hơn kết quả tốt nhất tìm được thì cập nhật
kết quả:
if nguyento(v) then
if (v>kq) then kq:=v;

Chương trình:
const finp='password.inp';
fout='password.out';

function nguyento(n: longint): boolean;


var i: longint;
begin
nguyento:=false;
if n<2 then exit;
for i:=2 to trunc(sqrt(n)) do
if (n mod i = 0) then
exit;

46
nguyento:=true;
end;

var t: string;
i, kq, v, j: longint;

begin
assign(input,finp);
reset(input);
assign(output,fout);
rewrite(output);

readln(t);
kq:=-1;
for i:=1 to length(t) do
begin
v:=0;
j:=i;
while (j<=length(t)) do
begin
if (t[j]<'1') or (t[j]>'9') then break;
v:=v*10+ord(t[j])-ord('0');
if v>=100000 then break;
writeln(v);
if nguyento(v) then
if (v>kq) then kq:=v;
inc(j);
end;
end;
writeln(kq);

close(input);
close(output);
end.

47

You might also like