Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 30

THUẬT TOÁN QUAY LUI

BACK TRACKING ALGORITHM

Thuật toán quay lui dùng để giải bài toán liệt kê các cấu hình. Mỗi cấu hình được xây
dựng bằng cách xây dựng từng phần tử, mỗi phần tử được chọn bằng cách thử tất cả các
khả năng.
Giả sử cấu hình liệt kê có dạng a[1..n], khi đó thuật toán quay lui thực hiện qua
các bước
1) Xét tất cả các giá trị a[1] có thể nhận, thử a[1] nhận lần lượt các giá trị đó. Với mỗi giá
trị thử gán cho a[1] ta sẽ:
2) Xét tất cả các giá trị a[2] có thể nhận, thử cho a[2] nhận lần lượt các giá trị đó. Với mỗi
giá trị thử gán cho a[2] lại xét tiếp các khả năng chọn a[3]... cứ tiếp tục như vậy cho đến
bước:
.....
n) Xét tất cả các giá trị a[n] có thể nhận, thử cho a[n] nhận lần lượt các giá trị đó, thông
báo cấu hình tìm được (a[1], a[2], ..., a[n])
- Thuật toán quay lui dùng để giải các bài toán liệt kê cấu hình như: xâu nhị phân, hoán
vị, tổ hợp, chỉnh hợp, cái túi, một số bài toán liên quan đến đồ thị.
- Thuật toán quay lui sử dụng rất nhiều trong các đề thi học sinh giỏi
- Thuật toán quay lui vét cạn mọi trường hợp nên thời gian chạy không nhanh. - thuật
toán quay lui chia thành 3 dạng:
+ Thuật toán quay lui tìm tất cả cả các nghiệm
+ Thuật toán quay lui tìm một nghiệm
+ Thuật toán quay lui tìm nghiệm tối ưu.
- Thuật toán quay lui dùng để giải bài toán liệt kê các cấu hình. Mỗi cấu hình được xây
dựng bằng cách xây dựng từng phần tử, mỗi phần tử được chọn bằng cách thử tất cả các
khả năng. Giả sử cấu hình cần liệt kê có dạng x[1..n], khi đó thuật toán quay lui thực hiện
qua các bước:

1
1. Xét tất cả các giá trị x[1] có thể nhận, thử cho x[1] nhận lần lượt các giá trị đó. Với
mỗi giá trị thử gán cho x[1] ta sẽ:
2. Xét tất cả các giá trị x[2] có thể nhận, lại thử cho x[2] nhận lần lượt các giá trị đó. Với
mỗi giá trị thử gán cho x[2] lại xét tiếp các khả năng chọn x[3],cứ tiếp tục như vậy đến
bước n
n. Xét tất cả các giá trị x[n] có thể nhận, thử cho x[n] nhận lần lượt các giá trị đó, thông
báo cấu hình tìm được (x[1], x[2], …, x[n]).
- Để giải các bài toán bằng thuật toán quay lui, thông thường ta thường dùng thủ tục đệ
quy Try(i : Integer) để chọn thành phần nghiệm xi .
- Lời gọi Try(1)
-Thuật toán quay lui dạng tổng quát
Procedure Try(i: Integer);
Begin
for (mọi giá trị j có thể gán cho xi) do
Begin (Thử cho xi := j)
If (xi là phần tử cuối cùng trong cấu hình) then (Thông báo cấu hình tìm
được)
Else
Begin (Ghi nhận việc cho xi :=j (Nếu cần));
Try(i + 1); {Gọi đệ quy để chọn tiếp xi+1}
(Nếu cần, bỏ ghi nhận việc thử xi := j, để thử giá trị khác);
End; End; End;

Mô hình của thuật toán quay lui được mô tả như sau

{thủ tục thử a[i] nhận lần lượt các giá trị mà nó có thể nhận}

Procedure TRY(i: integer);


Var j: integer;

2
Begin
For (mọi giá trị của j có thể gán cho a[i]) do
Begin
(Thử cho a[i]=j);
If (a[i] là phần tử cuối cùng trong cấu hình) THEN
(thông báo cấu hình tìm được)
else
Begin
(Ghi nhận việc cho a[i] nhận giá trị j nếu cần))
Try(i+1); {gọi đệ quy để chọn tiếp a[i+1]}
(nếu cần, bỏ ghi nhận việc thử a[i]:= j để thử giá trị khác);
End;
End;
End;
Thuật toán quay lui được bắt đầu bằng lời gọi try(1)
Ví dụ1: Liệt kê dãy nhị phân có độ dài n
Biểu diễn dãy nhị phân có độ dài n dưới dạng a[1..n]. ta sẽ liệt kê các dãy nhị phân bằng
cách thử dùng các giá trị {0,1} gán cho a[i]. Với mỗi giá trị thử gán cho a[i] lại thử các
giá trị có thể gán cho a[i+1].
Ví dụ 1:
Input Output
0
1
1
Ví dụ 2:
Input Output
00
01
2
10
11

3
Ví dụ 3:
Input Output
000
001
010
011
3
100
101
110
111

Chương trình liệt kê bằng thuật toán quay lui có thể viết như sau:
Var a:array[1..100] of integer;
n:integer; f:text;
procedure xuat;
var i:integer;
begin
for i := 1 to n do write(f,a[i]);
writeln(f);
end;
procedure try(i:integer);
var j: integer;
Begin
for j := 0 to 1 do {xét các giá trị có thể gán cho a[i]}
begin
a[i] := j; {thử a[i]}
if i=n then xuat {nếu thử đến cấu hình cuối thì in kết quả}
else
try(i+1); {nếu chưa phải cấu hình cuối thì gọi đệ quy chọn tiếp a[i+1]}

4
end;
end;
BEGIN
Assign(f,’nhiphan.inp’); reset(f);
Read(f,n); close(f);
Assign(f,’nhiphan.out’); rewrite(f);
Try(1); {Thử các cách chọn giá trị a[1]}
Close(f);
End.
Ví dụ với n=3, cây tìm kiếm quay lui như sau

const
nmax=9;
type
data = byte;
var
n:data;
res:array[1..nmax]of data;

procedure xuat;

5
var i:data;
begin
for i:=1 to n do
write(res[i]);
writeln;
end;

procedure try(i:data);
var j:data;
begin
if i>n then
xuat
else
for j:=0 to 1 do
begin
res[i]:=j;
try(i+1);
end;
end;

begin
readln(n);
try(1);
end.

Ví dụ 2: Liệt kê các chỉnh hợp không lặp chập k của n phần tử


Nhắc lại chỉnh hợp không lặp chặp k của n phần tử:
* Chỉnh hợp không lặp
Định nghĩa chỉnh hợp không lặp

Một chỉnh hợp không lặp chập k của n phần tử là một cách sắp xếp có thứ tự gồm k phần
tử khác nhau lấy từ n phần tử đã cho (với k < n).

Ví dụ: Có 5 chữ số 1, 2, 3, 4, 5. Hãy lập tất cả các số gồm 2 chữ số khác nhau?

Giải

Nếu bạn làm theo cách thủ công thì cứ ghép các số 1, 2, 3, 4, 5 với điều kiện loại bỏ 2 số
giống nhau:

6
11, 12, 13, 14, 15

21, 22, 23, 24, 25

31, 32, 33, 34, 35

41, 42, 43, 44, 45

51, 52, 53, 54, 55

Mỗi một số trên chính là một cách sắp xếp có thứ tự gồm 2 phần tử khác nhau lấy từ 5
phần tử đã cho. Vậy mỗi số trên chính là một chỉnh hợp không lặp chập hai của năm phần
tử. Sở dĩ ta bỏ đi các phần tử giống nhau bởi vì đây là chỉnh hợp không lặp, tức chỉnh hợp
này có phần tử không lặp lại.

Với ví dụ này là một trường hợp đơn giản, có thể làm thủ công như trên được. Nhưng hãy
tưởng tượng ở một câu hỏi khó hơn, có 10 số, 100 số , 1000 số,... và yêu cầu ta lập ra các
số có 3 chữ số, 30 chữ, số, 300 chữ số,... nếu làm theo cách thủ công thì chẳng biết đến khi
nào mới có kết quả. Do đó yêu cầu tất yếu là phải tìm ra một công thức tổng quát để tính
số chỉnh hợp không lặp.

Cách tính số chỉnh hợp không lặp

Số các chỉnh hợp không lặp chập k của n phần tử kí hiệu là Ank. Và ta cần đi xây dựng
công thức tính Ank:

Để tạo ra một chỉnh hợp không lặp chập k của n phần tử ta phải thực hiện một dãy liên tiếp
k hành động:

Hành động thứ nhất: Chọn 1 trong n phần tử để xếp đầu, có n cách.

Hành động thứ hai: Chọn 1 trong n-1 phần tử để xếp thứ hai, có n-1 cách.

...

Hành động thứ k: Chọn 1 trong n-k+1 phần tử để xếp cuối, có n-k+1 cách.

Theo quy tắc nhân, số cách tạo ra một chỉnh hợp không lặp chập k của n phần tử là:

Ank = n.(n-1)....(n-k+1)

Để dễ nhớ ta sử dụng công thức sau:

7
(n-k)...2.1 n!

Ank = n.(n-1)...(n-k+1) = n.(n-1)...(n-k+1).----------------- = ----------

(n-k)...2.1 (n-k)!

Var a:array[1..100] of integer;


B:array[1..100] of 0..1;
k,n:integer; f:text;
procedure xuat;
var i:integer;
begin
for i := 1 to k do write(f,a[i]);
writeln(f);
end;
procedure try(i:integer);
var j: integer;
Begin
for j := 1 to n do {xét các giá trị có thể gán cho a[i]}
if b[j]=0 then
begin
a[i] := j; {thử a[i]}
if i=k then xuat {nếu thử đến cấu hình cuối thì in kết quả}
else
begin
b[j]:=1;
try(i+1); {nếu chưa phải cấu hình cuối thì gọi đệ quy chọn tiếp a[i+1]}
b[j]:=0;
end;
end;

8
end;
BEGIN
Assign(f,’chinhhop.inp’); reset(f);
Read(f,k,n); close(f);
Assign(f,’chinhhop.out’); rewrite(fo);
Try(1); {Thử các cách chọn giá trị a[1]}
Close(f);
Để liệt kê các chỉnh hợp không lặp chập k của tập S = {1,2,3,.., n} , ta có thể đưa về liệt
kê các cấu hình

x[1,..,k] trong đó xi thuộc tập S và khác nhau từng đôi một.

Như vậy thủ tục Attempt(i) sẽ xét hết tất cả các khả năng xi chạy từ 1 -> n mà các phần
từ này chưa được phần tử đứng trước chọn.

Muốn biết giá trị nào đó đã được phần tử đứng trước chọn hay chưa ta dùng một mảng
đánh giá.

* Khởi tạo một mảng c[1...n] có kiểu logic boolean. Ở đây c[i] cho biết giá trị i còn tự do
hay đã được chọn rồi. Ban đầu khởi tạo các phần tử của mảng có giá trị TRUE, nghĩa là
các phần tử từ 1-> n đều tự do.

* Tại các bước chọn của x[i] ta chỉ chọn các giá trị mà c[i] = TRUE, tức là còn tự do.

* Trước khi gọi đệ qui x[i+1], đặt giá trị j vừa gán cho x[i] là đã chọn , tức là gán c[j] =
FALSE để các giá trị x[i+1],x[i+2],... không chọn j nữa.

*Quá trình này sẽ lặp lại đến khi ta chọn được phần tử x[k], lúc này ta sẽ in ra kết quả.

Ví dụ:
Input: n k output
3 2 12
13
21
23
31
32
4 2 12

9
13
14
21
23
24
31
32
34
41
42
43
4 3 123
124
132
134
142
143
213
214
231
234
241
243
312
314
321
324
341
342
412
413
421
423
431
432
5 2 12
13
14
15
21
23
24
25

10
31
32
34
35
41
42
43
45
51
52
53
54

{Thuat toan quay lui liet ke cac chinh hop khong lap chap k}

program chinhhopkhonglapchapk;

const
InputFile = 'ip.inp';
OutputFile = 'op.out';
max = 100;

var

x: array[1..max] of Integer;
c: array[1..max] of Boolean;
n,k : Integer;
f: Text;

procedure printResult; {Thu tuc in cau hinh tim duoc}

var

i : Integer;

begin

for i:= 1 to k do Write (f,x[i],' ');


WriteLn(f);

end;

procedure Attempt ( i: Integer); {Thu cac cach chon x[i] }

11
var j: Integer;
begin

for j:= 1 to n do

if c[j] then {Chi xet nhung gia tri con tu do }


begin
x[i] := j;
if i = k then printResult() {In cau hinh tim duoc}

else

begin
c[j] := FALSE; {Danh dau j da duoc chon }
Attempt(i+1); {Thu cac cach con x[i+1] }
c[j] := TRUE; {Bo dau danh chon j boi buoc tiep theo ta se xet gia tri khac cho
x[i]}
end;
end;
end;

begin

Assign( f,InputFile);Reset(f);
ReadLn(f,n,k);

Assign( f, OutputFile); Rewrite(f);


FillChar(c,SizeOf(c),TRUE); {Tat ca cac so deu chua duoc chon}

Attempt(1); {Tim cac kha nang chon cho x[1]}

Close(f);

end.

12
Liệt kê các Chỉnh hợp chập k của n phần tử

Đề bài: Nhập vào hai số nguyên dương n và k. Hãy liệt kê các chỉnh hợp chập k của n
phần tử.

const fi = 'CHINHHOPCHAPK.INP';
fo = 'CHINHHOPCHAPK.OUT';
MAXN = 20;
var f: text;
n, k: integer;
a: array [1..MAXN] of integer;
b: array [1..MAXN] of boolean;
dem: integer;

procedure Nhap;
begin
assign(f, fi); reset(f);
readln(f, n, k);
close(f);

fillchar(b, sizeof(b), true);


dem:= 0;
end;

procedure InKQ;
var i: integer;
begin
inc(dem);
write(f, dem:3, ': ');

13
for i:= 1 to k do
write(f, a[i]);
writeln(f);
end;

procedure Try2(i: integer); // dien gia tri cho vi tri thu i


var j: integer;
begin
for j:= 1 to n do
if b[j]=true then
begin
a[i]:= j;
b[j]:= false;
if i = k then InKQ
else Try2(i+1);
b[j]:= true;
end;
end;

BEGIN
Nhap;
assign(f, fo); rewrite(f);
Try2(1);
close(f);
END.
OUTPUT:

1: 123

14
2: 124

3: 125

4: 132

5: 134

6: 135

7: 142

8: 143

9: 145

10: 152

11: 153

12: 154

13: 213

14: 214

15: 215

16: 231

17: 234

18: 235

19: 241

20: 243

15
21: 245

22: 251

23: 253

24: 254

25: 312

26: 314

27: 315

28: 321

29: 324

30: 325

31: 341

32: 342

33: 345

34: 351

35: 352

36: 354

MỘT SỐ BÀI TẬP CÙNG DẠNG


Bài 1: Liệt kê các chỉnh hợp lặp chập k của n phần tử
Bài 2: Liệt kê các tập con k phần tử của n số nguyên đầu tiên.(các phần tử khác nhau)

16
Bài 3: Từ ví dụ 1 ở trên hãy chỉnh sửa để được dãy nhị phân có độ dài n nhưng không có
2 số 0 kề nhau.
Bài 4: Cho xâu s chỉ gồm các kí tự từ 'A' đến 'Z' (các kí tự đôi một khác nhau). Hãy liệt
kê tất cả các hoán vị khác nhau của xâu s.
* Bài phân tích số:
Cho một số nguyên dương n<=30 , hay tìm tất cả các cách phân tích số n thành tổng
của các số nguyên dương, các cách phân tích là hoán vị của nhau chỉ tính là 1 cách.
Cách làm:
Ta sẽ lưu nghiệm trong mảng x, ngoài ra còn có một mảng t. Mảng t xây dựng như sau:
t[i] sẽ là tổng các phần tử trong mảng x từ x[1] đến x[i].
t[i] := x[1] + x[2] + .... + x[3];
Khi liệt kê các dãy có tổng đúng bằng n, để tránh sự trùng lặp, ta đưa thêm điều kiện x[i -
1]<=x[i];
Vì số phần tử của mảng x là không cố định nên thủ tục printResult phải có thêm tham số
cho biết phải in ra bao nhiêu phần tử.
Thủ tục đệ quy sẽ thử các giá trị có thể nhận Attempt( i) của x[i] thỏa mãn x[i] >= x[i-1];
Khi nào thì in kết quả và khi nào gọi đệ quy tiếp?
t[i -1] là tổng tất cả các phần tử từ x[1] đến x[i-1].
* Khi t[i] = n ( tức là x[i] = n- t[i-1] thì in ra kết quả)
* Khi tìm tiếp x[i+1] không được nhỏ hơn x[i] và t[i+1] là tổng tất cả các phần tử từ x[1]
đến x[i+1] không được vượt quá n.
Ta có:
t[i+1] <= n
<=> t[i-1] + x[i] + x[i+1] <=n
<=> x[ i +1] + x[i] <= n - t[i-1]
<=> x[i] <= ( n - t[i-1])/2
* Một cách dễ hiểu : Ta gọi đệ quy khi giá trị x[i] được chọn còn cho phép chọn một
phần tử khác lớn hơn hoặc bằng nó mà không làm tổng vượt quá n. Còn in ra kết quả khi
x[i] đúng bằng phần thiếu hụt của t[i-1] so với n.

17
Vậy thủ tục Attempt(i) thử các giá trị cho x[i] có thể viết như sau:
( Để tổng quát cho i =1, ta gán x[0] := 1 và t[0] := 0)
* Xét các giá trị x[i] từ x[i-1] đến (n-x[i-1] ) div 2, cập nhật t[i] = t[i-1] + x[i] và gọi đệ
quy tìm tiếp.
* Cuối cùng ta xét x[i] = n - t[i-1] và in ra kết quả từ x[1] đến x[i];
input output Giải thích
4 4= Có 5 cách phân tích
1+1+1+1
4=
1+1+2
4=
1+3
4=
2+2
4=
4
5 5= Có 7 cách phân tích
1+1+1+1+1
5=
1+1+1+2
5=
1+1+3
5=
1+2+2
5=
1+4
5=
2+3

18
5=
5
Chương trình minh họa:
program Analyse;
const
max = 100;
InputFile = 'ip.INP';
OutputFile = 'op.OUT';
var
n: Integer;
x : array[0..max] of Integer;
t : array[0..max] of Integer;
f : Text;

procedure Init; {Khoi tao }


begin
Assign( f,InputFile);Reset(f);
ReadLn(f,n);
Close(f);
x[0] := 1;
t[0] := 0;

end;

procedure printResult ( k: Integer);


var

i: Integer;
begin
WriteLn(f,n,' = ');
for i:= 1 to k-1 do Write(f,x[i],'+');
WriteLn(f,x[k]);
end;

procedure Attempt( i: Integer);

var j : Integer;
begin
for j:= x[i-1] to (n-t[i-1])div 2 do
{Trong truong hop con chon tiep duoc x[i+1]}
begin
x[i] := j;

19
t[i] := x[i] + t[i-1];
Attempt(i+1);
end;
x[i] := n -t[i-1];
{ Neu x[i] la phan tu cuoi thi no bat buoc phai bang
n- t[i-1]}
printResult(i);
end;

begin

Init;
Assign(f,OutputFile); Rewrite(f);
Attempt(1);
Close(f);

end.
Bài 5: Cho số nguyên dương n (n<=20). Hãy liệt kê tất cả các xâu độ dài n chỉ gồm 2 kí
tự ‘A’ hoặc ‘B’ mà không có kí tự ‘B’ nào đứng cạnh nhau.
Bài 6: Bài toán rút tiền tự động ATM
Một máy rút tiền tự động ATM có n (n<=20) tờ tiền có giá trị t1,t2,…,tn. Hãy đưa ra một
cách trả với số tiền đúng bằng S.
Dữ liệu vào từ file “ATM.INP” có dạng:
- dòng đầu là 2 số n và s
- dòng 2 gồm n số t1,t2,…,tn
Kết quả ra file “ATM.OUT” có dạng: Nếu có thể trả đúng S thì đưa ra cách trả, nếu
không thì ghi -1
ATM.INP ATM.OUT
10 390 20 20 50 50 50 100 100
200 10 20 20 50 50 50 50 100 100
CODE THAM KHẢO:

var t:array[1..100] of longint;


a,x:array[1..100] of 0..1;
i,n:integer; sum,s:int64;
kt:boolean;
fi,fo:text;
20
procedure xuat;
var i:integer;
begin
sum:=0;
for i:=1 to n do
sum:=sum+a[i]*t[i];
if sum=s then
begin
x:= a;
kt:=true;
exit;
end;
end;

procedure try(i:integer);
var j:integer;
begin
for j:=0 to 1 do
if kt=false then
begin
a[i]:=j;
if i=n then xuat else try(i+1);
end;
end;

begin
assign(fi,'atm.inp'); reset(fi);
assign(fo,'atm.out'); rewrite(fo);
readln(fi,n,s); kt:=false;
for i:=1 to n do read(fi,t[i]);
try(1);
if kt then
begin
for i:=1 to n do
if x[i]=1 then write(fo,t[i],' ');
end
else write(fo,-1);
close(fO);
end.

21
Bài 7: Cho một xâu S (Chỉ gồm các kí tự ‘0’ đến ‘9’ độ dài nhỏ hơn 10 và số nguyên M,
hãy đưa ra tất cả các cách chèn vào S các dấu ‘+’ hoặc ‘-‘ để thu được số M cho trước
nếu không có phương án nào thỏa mãn thì ghi là khong the chen.
Dữ liệu vào từ tệp PHEPTOAN.INP gồm 2 dòng
- dòng 1 xâu S
- dòng 2 số nguyên M
Kết quả ra file PHEPTOAN.OUT tất cả các cách chèn hoặc khong the chen

PHEPTOAN.INP PHEPTOAN.OUT
1234567 -1-2+3+4+5-6+7
10 -1+2-3+4-5+6+7
+1-2-3-4+5+6+7
+1-2+3+4+5+6-7
+1+2-3+4+5-6+7
+1+2+3-4-5+6+7
12345 Khong the chen
6

Chỉnh sửa lại chương trình để đưa ra một cách chèn thỏa mãn nếu có thể
ví dụ
M=8, s=’1234567’ một cách chèn ‘-1-2-3-4+5+6+7’

Uses crt;
Const fo = ‘chenxau.out’;
dau: array[1…3] of String[1]= (’’, ‘-’, ‘+’);
s:array[1…9] of char=(‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’);
Var d:array[1…9] of String[1];
m:longInt;
f:text;

22
k:integer;
found:boolean;
Procedure Init;
Begin
Write(‘Cho M=’);
Readln(m);
found:=false;
end;
Function tinh(s:string):longint;
Var i,t:longint;
code:integer;
Begin
i:=length(s);
While not(s[i] in [’-’,’+’]) and (i>0) do dec(i);
val(copy(s,i+1,length(s)-i),t,code);
If i=0 then begin tinh:=t; exit; end
else
begin
delete(s,i,length(s)-i+1);
If s[i]=’+’ then tinh:=t+tinh(s);
If s[i]=’-’ then tinh:=tinh(s)-t;
end;
End;
Procedure Test(i:integer);
Var st:string; j:integer;
Begin
st:=’’;
For j:=1 to i do st:=st+d[j]+s[j];

23
If Tinh(st) = m then begin writeln(f,st); found:=true; end;
End;
Procedure Try(i:integer);
Var j:integer;
Begin
for j:=1 to 3 do
begin
d[i]:=dau[j]; Test(i);
If i<9 then try(i+1);
end;
End;
BEGIN
Clrscr;
Init;
Assign(f,fo);Rewrite(f);
for k:=1 to 2 do
begin
d[1]:=dau[k];
Try(2);
end;
If not found then write(f,‘khong co ngiem’);
Close(f);
END.

Bài 8: Lập chương trình liệt kê tất cả các tổ hợp chập k của n phần tử
Dữ liệu vào: từ tệp TOHOP.INP gồm 2 số nguyên k và n (k<n)
Dữ liệu ra: Ghi tất cả các tổ hợp liệt kê được vào tệp TOHOP.OUT, mỗi phương án ghi
trên một dòng.
TOHOP.INP TOHOP.OUT

24
24 12
13
14
23
24
34
Phát triển bài toán thành liệt kê tất cả tổ hợp chập k của dãy a1,a2,…,an
Bài 9: Một xâu X=x1,x2,…,xm được gọi là xâu con của xâu Y=y1,y2,…,yn nếu ta có thể
nhận được xâu X từ xâu Y bằng cách xóa đi một số kí tự, tức là tồn tại một dãy các chỉ
số:
1<=i1<i2<…<im<=N để x1=yi1, x2=yi2, xm=yim

Ví dụ: X=’adz’ là xâu con của xâu Y=’baczdtz’ ; i1=2<i2=5<i3=7.


Đọc xâu từ tệp xaucon.inp độ dài không quá 15 kí tự chỉ gồm các kí tự ‘a’ đến ‘z’(các kí
tự trong xâu là khác nhau).
Hãy liệt kê tất cả các xâu con khác nhau của xâu s và ghi vào tệp xaucon.out
Bài 10: Tiền khách sạn (đề thi học sinh giỏi tỉnh Bình phước năm 2015)

Trong dịp nghi lễ 30 tháng 4 và 1 tháng 5 vừa qua do cùng đợt nghỉ với ngày giỗ tổ
Hùng Vương 10 tháng 3(âm lịch) nên số ngày nghỉ lễ tăng lên. Vì thế lượng khách du
lịch đổ về Nha Trang tham quan cũng tăng kỷ lục, dẫn đến tinh trạng các khách sạn ở đây
“cháy phòng”.
Khách sạn Quang Huy chỉ còn một phòng nên quyết định cho thuê phòng này theo
hình thức thỏa thuận về giá cả. Sau khi tổng hợp các đơn đặt hàng, khách sạn nhận
được n đơn đặt hàng, trong đó đơn đặt hàng thứ i đăng ký ngày bắt đầu là a i, ngày trả
phòng là bi và chấp nhận trả số tiền thuê phòng là ci.

25
Do có nhiều đơn đặt hàng, thời gian đặt phòng lại chồng chéo nhau, số tiền khách
hàng chấp nhận trả cho khách sạn cũng khác nhau nên ban quản lý khách sạn đang rất
khó khăn không biết nhận lời hay từ chối khách hàng nào.
Yêu cầu: Viết chương trình giúp khách sạn nhận đơn đặt phòng sao cho lợi nhuận thu
được là lớn nhất.
Lưu ý: Theo điều lệ của khách sạn, khách hàng phải ưả phòng trước 12 giờ trưa, khách
hàng khác có thể nhận phòng từ 12 giờ trong một ngày.
Dữ liệu vào: được ghi trên tệp tienks.inp bao gồm:
o Dòng thứ nhất là số nguyên n (1 ≤ n ≤ 12000) thể hiện số đơn đặt hàng.
o n dòng tiếp theo gồm 3 số nguyên ai, bi và ci. Mỗi số cách nhau một khoảng trắng với
ràng buộc(l ≤ai ≤ bi ≤ 100, 0 ≤ c ≤1000).
Dữ liệu ra: lưu trong tệp tienks.out với một số nguyên thể hiện số tiền lớn
Ví dụ:

TIENKS.INP TIENKS.OUT
3 20
128
Vi dụ 1
236
476
4 17
145
Vi dụ 2 138
354
469
var i,t,d,n,k:integer; s:byte;
max,tks,vao,ra,a:array[0..100] of integer;
b:array[0..100] of 0..1;

procedure xuat;
var i:byte;
begin

26
d:=d+1; max[d]:=0;{reset để tính max hoán vị mới}
for i:=1 to k do max[d]:=max[d]+tks[a[i]];
end;

procedure try(i:byte);
var j:byte;
begin
for j:=1 to n do
if b[j]=0 then
begin
a[i]:=j;
if a[i]>a[i-1] then
if vao[a[i]]>=ra[a[i-1]] then
if i=k then xuat {kiem tra tuan tu cac hoan vi}
else
begin
b[j]:=1;
try(i+1);
b[j]:=0;
end;
end;
end;

BEGIN
assign(fi,'tienks.inp'); reset(fi);
assign(fo,'tienks.out'); rewrite(fo);
readln(fi,n);
for i:=1 to n do
readln(fi,vao[i],ra[i],tks[i]);
d:=0;

for k:=1 to n do try(1); {xét tất cả hoán vị từ 1-n}

t:=max[1];
for i:=2 to d do if max[i]>t then t:=max[i];
write(fo,t);
close(fo);
end.

Bài 11: Dãy số


Cho dãy số nguyên a1,a2,..an. số ap (1 < p < n) được gọi là một số trung bình cộng trong
dãy nếu tồn tại 3 chỉ số i, j, k (1 < i, j, k < n) đôi một khác nhau,

27
sao cho ap = (ai + aj + ak)/3

Yêu cầu: Cho n và dãy số a1 a2,.. an„. Hãy tìm số lượng các số trung bình cộng trong dãy.

Dữ liệu vào: Từ tệp TBC.INP

- Dòng đầu ghi số nguyên dương n (3 < n < 1000)


- Dòng thứ hai chứa n số nguyên ai (|ai|< 108) mỗi số cách nhau bởi dấu cách.

Kết quả ra : Ghi vào tệp TBC.OUT


Số lượng các số trung Bình cộng trong dãy.
ví dụ:

TBC.INP TBC.OUT

5 2

43635

7 5

6452897

CODE THAM KHẢO

VAR a:array[0..1000] of integer;


t,x:array[0..1000] of longint;
b:array[0..1000] of 0..1;
dem,i,j,kq,n:integer;
fi,fo:text;

Procedure xuat;
var tong,i,j:integer;
begin
tong:=0;

28
for i:=1 to 3 do tong:=tong+x[a[i]];
if tong mod 3 = 0 then {Tìm các phần tử chia hết cho 3}
begin
inc(dem);
t[dem]:=tong;
end;
end;

Procedure try(i:integer);
var j:integer;
begin
for j:=1 to n do
if b[j]=0 then
begin
a[i]:=j;
if a[i]>a[i-1] then
if i=3 then xuat else
begin
b[j]:=1;
try(i+1);
b[j]:=0;
end;
end;
end;
BEGIN
kq:=0;
assign(fi,'tbc.inp'); reset(fi);
assign(fo,'tbc.out'); rewrite(fo);
readln(fi,n);
for i:=1 to n do read(fi,x[i]);
try(1);
for i:=1 to n do {duyệt dãy ban đầu}

29
for j:=1 to dem do
if t[j] div 3=x[i] then
{nếu có tổng chia hết thì tăng biến đếm}
begin
inc(kq); break;
{Dem xong thoat khoi vong lap 2 vi Moi phan tu chi dem 1 lan}
end;
write(fo,kq);
close(fo);
end.

30

You might also like