Professional Documents
Culture Documents
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;
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.
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}
Chương trình:
const
finp='quangcao.inp';
fout='quangcao.out';
maxn=15; maxm=110;
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;
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.
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.
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';
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.
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 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;
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.
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;
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.
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.
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;
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);
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.
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.
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 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;
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.
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.
25
function max(a,b:longint): longint;
begin
if a>b then max:=a else max:=b;
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.
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.
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.
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.
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';
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.
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;
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.
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.
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;
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.
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';
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.
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';
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