Tải bản đầy đủ (.pdf) (84 trang)

Tai lieu boi duong doi tuyen quoc gia 2013 3

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

Page 0 of 84

MỤC LỤC
Phương pháp duyệt
Chuyên đề 1. Duyệt vét cạn.................................................................................................................. 1
Chuyên đề 2. Duyệt nhánh cạnh ..................................................................................................... 12
Chuyên đề 3. Duyệt ưu tiên............................................................................................................... 24
Tìm kiếm nhị phân

Chuyên đề 4. Tìm kiếm nhị phân và ứng dụng......................................................................... 27
Xử lý bít

Chuyên đề 5. Xử lý bit.......................................................................................................................... 30
Quy hoạch động

Chuyên đề 6. Quy hoạch động cơ bản .......................................................................................... 36
Chuyên đề 7. Quy hoạch động trạng thái.................................................................................... 49
Đồ thị

Chuyên đề 8. Tìm kiếm theo chiều rộng ..................................................................................... 58
Chuyên đề 9. Tìm kiếm theo chiều sâu........................................................................................ 69

Võ Văn Trị - CQB | Confidential


PHƯƠNG PHÁP DUYỆT
Chuyên đề 1. Duyệt vét cạn - Backtracking
Quay lui, vét cạn, thử sai, duyệt… là một số tên gọi tuy không đồng nghĩa nhưng
cùng chỉ một phương pháp trong tin học: tìm nghiệm của một bài toán bằng cách
xem xét tất cả các phương án có thể. Đối với con người phương pháp này thường
không khả thi vì số phương án cần kiểm tra lớn. Tuy nhiên đối với máy tính, nhờ


tốc độ xử lý nhanh, máy tính có thể giải rất nhiều bài toán bằng phương pháp
quay lui vét cạn. Người đầu tiên đề ra chiến lược này là nhà toán học người Mỹ
Derrick Henry Lehmer (1905 – 1991) vào những năm 1950.

Ưu điểm của phương pháp quay lui, vét cạn là luôn bảo đảm tìm ra nghiệm đúng,
chính xác. Tuy nhiên, hạn chế của phương pháp này là thời gian thực thi lâu, độ
phức tạp lớn.
Về bản chất, tư tưởng của phương pháp này là thử từng khả năng cho đến khi tìm
thấy lời giải đúng. Đó là một quá trình tìm kiếm theo độ sâu trong một tập các lời
giải. Trong quá trình tìm kiếm, nếu gặp một hướng lựa chọn không thỏa mãn, ta
quay lui, về điểm lựa chọn có các hướng khác và thử hướng lựa chọn tiếp theo.
Khi đã thử hết tất cả các hướng lựa chọn xuất phát từ một điểm, ta quay lại điểm
lựa chọn kế trước. Quá trình tìm kiếm kết thúc khi không còn hư ớng lựa chọn nào
để thử.

Chiến lược quay lui tương tự với tìm kiếm theo độ sâu nhưng tốn ít không gian
nhớ hơn, thời gian tìm kiếm lại nhanh hơn.
Mô hình thuật toán Backtracking:

procedure try(i:integer);
begin
for <mọi giá trị t có thể gán cho xi> do
begin
<Thử cho xi := t>;
if <thành công> then <thông báo kết quả>
else
begin
<Ghi nhận việc xi nhận giá trị t>;
<try(i+1)>; {th bước đi tiếp theo}
<Hủy bước đi thứ i (nếu cần)>;

end;
end;
end;

1. Bài tập 1: Liệt kê tất cả các dãy nhị phân có độ dài n.
Võ Văn Trị - CQB | Confidential


Page 2 of 84
Phân tích:
Dãy nhị phân có độ dài n có dạng (x1, x2, …, xn) với xi ∈ {0, 1}, 1 ≤ ≤ . Như vậy,
tại bước thứ i, ta tìm cách chọn giá trị cho biến x i.
{Chuong trinh cai dat thuat toan de quy}
{Liet ke tat ca cac cau hinh nhi phan co do dai n}
Program BackTracking_ListsOfBinaryPermutations;
Const
n=3;
var
i,k:byte;
h:array[1..20] of byte;
Procedure Try(i:byte);
var
j:byte;
begin
for j:=0 to 1 do
begin
h[i]:=j;
if i=n then
begin
for k:=1 to n do write(h[k]);

writeln;
end
else
Try(i+1);
end;
end;
begin
try(1);
end.

2. Bài tập 2: Liệt kê tất cả các hoán vị của tập {1, 2, …, n}

Phân tích:

Các hoán vị của tập {1, 2, …, n} có dạng (x1, x2, …, xn) với xi ∈ {1, 2, … , }, 1 ≤ ≤
và xi # xj với mọi i, j.

- Như vậy, việc xây dựng mỗi hoán vị được thực hiện qua n bước.
- Bước thứ i ta chọn một số trong tập {1, 2, …, n} để đặt vào vị trí thứ i
của hoán vị.
- Tuy nhiên, ta chỉ chọn được số x i khi nó chưa được chọn cho các vị trí

KT

trước đó.
Ta sử dụng mảng đánh dấu như sau:

False
1


True
2

True
3

….

Với ý nghĩa:
- KT[i] = True nếu i chưa được chọn





Võ Văn Trị - CQB | Confidential


n


- KT[i] = False nếu i đã được chọn

{Chuong trinh cai dat thuat toan de quy}
{Liet ke tat ca cac hoan vi cua tap (1,2,...,n)}
Program BackTracking_PermutationLists;
Uses crt;
Const
n=3;
var

i,k:byte;
kt:array[1..20] of boolean;
h:array[1..20] of byte;
Procedure Try(i:byte);
var
j:byte;
begin
for j:=1 to n do
if kt[j] then
begin
h[i]:=j;
kt[j]:=false;
if i=n then
begin
for k:=1 to n do write(h[k],' ');
writeln;
end
else
Try(i+1);
kt[j]:=true;
end;
end;
begin
clrscr;
fillchar(kt[1],n,true);
try(1);
end.

3. Bài tập 3: Tổ hợp
Một tổ hợp chập k của n là một tập con k phần tử của n phần tử.

Chẳng hạn tập {1, 2, 3, 4} có các tổ hợp chập 2 là:

{1, 2}, {1, 3}, {1, 4}, {2, 3}, {2, 4}, {3, 4}

Vì trong tập hợp các phần tử không phân biệt thứ tự nên tập {1, 2} cũng là tập
hợp {2, 1}, do đó ta coi chúng chỉ là một tổ hợp.
Yêu cầu: Hãy xác định tất cả các tổ hợp chập k của tập n phần tử.
 Phân tích:

Võ Văn Trị - CQB | Confidential


Page 4 of 84
Mỗi tổ hợp chập k của n phần tử có dạng (x 1, x2, …, xk) trong đó:

xi là một giá trị nằm trong tập {1, 2, …, n}.
vì tập hợp không phân biệt thứ tự nên ta sắp xếp các phần tử theo thứ tự
tăng dần, vì vậy ràng buộc x i < xi+1 với mọi giá trị i từ 1 đến k -1.
- ứng cử viên xi được chọn từ tập giá trị {x i-1 + 1, ...., n – (k-i)}
- nguyên tắc chọn trên giúp ta khỏi cần phải dánh dấu, bởi vì việc chọn giá
trị tiếp theo phần tử đã được chọn trước đó nên sẽ không bị lặp lại.
 Chương trình
-

{chuong trinh de quy quay lui}
{liet ke tat ca cac to hop chap k cua tap n phan tu}
program BackTracking_PermutationLists;
const
n=4;
k=3;

var
h:array[1..20] of byte;
i:byte;
procedure ghinghiem;
var
j:byte;
begin
write('{');
for j:=1 to k do write(h[j],' ');
writeln('}');
end;
procedure try(i:byte);
var
j:byte;
begin
for j:= h[i-1]+1 to n-(k-i) do
begin
h[i]:=j;
if i=k then ghinghiem
else try(i+1);
end;
end;
begin
try(1);
readln;
end.

4. Bài tập 4: Chỉnh hợp lặp
Chỉnh hợp lặp chập k của n phần tử là một dãy gồm k thành phần, mỗi thành phần
là một phần tử của tập n phần tử, không yêu cầu các thành phần là khác nhau và

có phân biệt thứ tự của các cấu hình.

Ví dụ, một dãy nhị phân có độ dài 3 là một chỉnh hợp lặp chập 3 của tập 2 phần tử
{0, 1}. Các dãy nhị phân có độ dài 3:
000, 001, 010, 011, 100, 101, 111

Võ Văn Trị - CQB | Confidential


Vì có phân biệt thứ tự nên cấu hình 001 khác với 010.

Như vậy, cách tìm nghiệm của bài toán chỉnh hợp lặp như sau:
-

Mỗi chỉnh hợp lặp chập k của tập n phần tử có dạng (x 1, x2, …, xk).
xi là một giá trị lấy trong tập {1, 2, …, n}.
không có ràng buộc nào giữa các thành phần.

{chuong trinh de quy quay lui}
{liet ke tat ca cac chinh hop lap chap k cua n}
program Backtracking_chinhhoplap;
const
n=2;
k=3;
var
h:array[1..20] of byte;
i:byte;
procedure ghinghiem;
var
j:byte;

begin
for j:=1 to k do write(h[j],' ');
writeln
end;
procedure chinhhoplap(i:byte);
var
j:byte;
begin
for j:=1 to n do
begin
h[i]:=j;
if i=k then ghinghiem
else chinhhoplap(i+1);
end;
end;
begin
chinhhoplap(1);
readln
end.

5. Bài tập 5: Chỉnh hợp không lặp
Đối với chỉnh hợp lặp, các thành phần được phép lặp lại. Đối với chỉnh hợp không
lặp chập k của n phần tử, các thành phần không được phép lặp lại. Ví dụ, có n
người, một cách chọn ra k người để xếp thành 1 hàng là một chỉnh hợp không lặp
chập k của n phần tử.

Một trường hợp đặc biệt của chỉnh hợp k hông lặp là hoán vị. Hoán vị của một tập
n phần tử là chỉnh hợp không lặp chập n của n.
Như vậy, cách tìm nghiệm của bài toán như sau:


- Mỗi nghiệm của bài toán là một dãy gồm k thành phần (x 1, x2, …, xk)

Võ Văn Trị - CQB | Confidential


Page 6 of 84
- Mỗi thành phần x i là một giá trị lấy trong tập {1, 2, …, n}

- Các giá trị đôi một khác nhau, để kiểm soát ràng buộc này, ta sử dụng mảng đánh
dấu ok với ý nghĩa như sau:
 Ok[j] = true có nghĩa rằng j sẽ chọn được
 Ok[j] = false thì không chọn được, vì đã chọn j cho các vị trí trước đó.

* Chương trình như s au:

{Chuong trinh de quy quay lui
liet ke tat ca cac chinh hop ko lap}
program Backtracking_chinhhopkolap;
const
n=2;
k=2;
var
ok:array[1..20] of boolean;
h:array[1..20] of byte;
i:byte;
procedure ghinghiem;
var
j:byte;
begin
for j:=1 to k do write(h[j],' ');

writeln;
end;
procedure Chinhhopkolap(i:byte);
var
j:byte;
begin
for j:=1 to n do
if ok[j] then
begin
h[i]:=j;
ok[j]:=false;
if i=k then ghinghiem
else chinhhopkolap(i+1);
ok[j]:=true;
end;
end;
begin
fillchar(ok[1],n,true);
chinhhopkolap(1);
readln;
end.

6. Bài tập 6: Bài toán xếp tám quân hậu
Một bàn cờ quốc tế là một bảng hình vuông gồm 8 hàng, 8 cột. Quân hậu là một
quân cờ có thể ăn được bất kỳ quân nào trên cùng một hàng, cùng một cột hay
một đường chéo. Hãy xếp tám quân hậu trên bàn cờ sao cho không có quân hậu
nào có thể ăn được quân hậu nào.
Giải :

Võ Văn Trị - CQB | Confidential



-

Theo bài toán, trên mỗi cột (j=1..8) của bàn cờ sẽ được đặt 1 quân hậu.

Như vậy với mỗi cột ta phải tìm dòng thứ i nào đấy để đặt quân hậu vào. Có
8 phương án chọn dòng i: i=1, 2, …, 8
Ta có thể đặt quân hậu vào ô [i,j] khi:
-

-

-

Đường chéo song song với đường chéo chính đi qua ô [i,j] chưa có
hậu
Đường chéo song song với đường chéo phụ đi qua ô [i,j] chưa có hậu

Khi đó, thủ tục try(j) thực hiện nhiệm vụ chọn dòng thứ i nào đấy để đặt
quân hậu thứ j vào ô [i,j]
Ta có thể minh họa một bàn cờ như sau:

Nhìn vào bàn cờ, ta có thể thấy:
-

-

Dòng i chưa có hậu


Các ô nằm trên đường chéo // với đường chéo chính thì hiệu của chỉ
số dòng và chỉ số cột là không đổi;

Các ô nằm trên đường chéo // với đường chéo phụ thì tổng của chỉ
số cột và chỉ số dòng là không đổi;

Vì thế, ta có thể đánh dấu trạng thái của bàn cờ như sau:
-

Đánh dấu trạng thái dòng

-

Đánh dấu trạng thái các đường chéo // với đường chéo chính
Võ Văn Trị - CQB | Confidential


Page 8 of 84

-

-

Đánh dấu trạng thái các đường chéo // với đường chéo phụ

Để lưu giữ kết quả, ta sử dụng mảng một chiều x

{Chuong trinh de quy quay lui
liet ke moi nghiem cua bai toan 8 quan hau}
program eightQueen;

const
fo='equeen.out';
var
{danh dau trang thai dong}
a:array[1..8] of boolean;
{danh dau trang thai duong cheo // cheo chinh}
b:array[-7..7] of boolean;
{danh dau trang thai duong cheo // cheo phu}
c:array[2..16] of boolean;
x:array[1..8] of byte;
j:byte;
f:text;
procedure ghinghiem;
var
i:byte;
begin
for i:=1 to 8 do write(f,x[i], ' ');
writeln(f);
end;
procedure equeen(j:byte);
var
i:byte;
begin
for i:=1 to 8 do
if a[i] and b[i-j] and c[i+j] then

Võ Văn Trị - CQB | Confidential


begin

x[j]:=i;
a[i]:=false;
b[i-j]:=false;
c[i+j]:=false;
if j=8 then ghinghiem
else equeen(j+1);
a[i]:=true;
b[i-j]:=true;
c[i+j]:=true;
end;
end;
begin
fillchar(a[1],8,true);
fillchar(b[-7],15,true);
fillchar(c[2],15,true);
assign(f,fo);
rewrite(f);
equeen(1);
close(f);
end.

7. Bài tập 7: Bài toán máy rút tiền tự động ATM
Một máy ATM hiện có n ( ≤ 20) tờ tiền có giá t 1, t2, …, tn. Hãy đưa ra một cách trả
với số tiền đúng bằng s.
Input: tệp ATM.INP có dạng:
-

Dòng 1: ghi hai số n và S;
Dòng 2: ghi n số t1, t2, …, tn


Output: ghi ra tệp ATM.OUT có dạng: Nếu có thể trả đúng S thì đưa ra cách trả,
nếu không ghi -1.
ATM.INP

10 390
200
10
20
50 100 100

20

20

50

50

20

ATM.OUT
50 50 100

100

50

Nghiệm của bài toán là một dãy nhị phân độ dài n, trong đó thành phần thứ i bằng
1 nếu tờ tiền thứ i được sử dụng, bằng 0 trong trường hợp ngược lại. (x 1, x2, …, xn)
là nghiệm nếu 1 1 + 2 2 + ⋯ +

.
* Chương trình:

{Chuong trinh de quy quay lui
giai bai toan may rut tien tu dong ATM}
Program AtmSolution;
const
fi='atm.inp';

Võ Văn Trị - CQB | Confidential


Page 10 of 84
fo='atm.out';
var
{mang t bieu dien menh gia cua cac to tien}
t:array[1..20] of integer;
{mang x ghi nhan ket qua}
x:array[1..20] of byte;
n:integer; i:byte; sum,s:longint;
ok:boolean; f:text;
procedure docdl;
var
j:byte;
begin
assign(f,fi); reset(f);
readln(f,n,s);
for j:=1 to n do read(f,t[j]);
close(f);
end;

procedure ghinghiem;
var
j:byte;
begin
ok:=true;
for j:=1 to n do
if x[j]=1 then write(f,t[j],' ');
writeln(f);
end;
procedure atm(i:integer);
var
j:byte;
begin
for j:=0 to 1 do
begin
sum:=sum+j*t[i];
x[i]:=j;
if sum = s then ghinghiem;
if (sumif ok=true then exit;
sum:=sum-j*t[i];
end;
end;
begin
docdl;
ok:=false; sum:=0;
fillchar(x[1],n,0);
assign(f,fo); rewrite(f);
atm(1);
if ok=false then writeln(f,-1);

close(f);
end.

Bài tập luyện tập
1.1 Bài toán mã đi tuần (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)
Võ Văn Trị - CQB | Confidential


Cho bàn cờ n x n ô, tìm cách di chuyển một quân mã (mã di chuyển theo luật cờ
vua) trên bàn cờ xuất phát từ ô (1, 1) đi qua tất cả các ô, mỗi ô qua đúng 1 lần.
Ví dụ với n = 5:

1

24

13

18

20

15

4

11

14
9

3

19
2

10

8

23
21

7

25

12

16

5

6

17
22

1.2 Số siêu nguyên tố (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)
Số siêu nguyên tố là số nguyên tố mà khi bỏ bớt một số tùy ý các chữ số bên phải
của nó thì phần còn lại vẫn là một số nguyên tố.


Ví dụ 2333 là một số siêu nguyên tố có 4 chữ số vì 233, 23, 2 cũng là các s ố
nguyên tố. Cho số nguyên dương N (0 < < 10), đưa ra các số siêu nguyên tố có
N chữ số cùng số các số đó.
Ví dụ với N = 4 có 16 số:

2333, 2339, 2393, 2399, 2939, 3119, 3137, 3733, 3739, 3793, 3797, 5939, 7193,
7331, 7333, 7393
1.3 Chèn phép toán (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)

Cho một xâu S chỉ gồm các ký tự từ ‘0’ đến ‘9’, độ dài nhỏ hơn 10 và số nguyên m,
hãy đưa ra một cách chèn vào S các dấu ‘+’ hoặc ‘ -‘ để thu được số m cho trước
(nếu có thể).
Ví dụ, M=8, S = ‘123456789’ thì một cách chèn là: ‘ -1+2-3+4+5-6+7-8+9’
1.4 Phân tích số (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)

Cho n số nguyên dương a1, a2, …, an ( ≤ 10, ≤ 109 ). Tìm số nguyên dương m
nhỏ nhất sao cho m không phân tích được dưới dạng tổng của một số các số
thuộc n số trên (mỗi số sử dụng không quá một lần).
Dữ liệu vào
n = 4
dãy số:
1, 2, 3, 6

Kết quả
13

Võ Văn Trị - CQB | Confidential



Page 12 of 84

Chuyên đề 2. Duyệt nhánh cận
Trong thực tế, có nhiều bài toán yêu cầu tìm ra một phương án thỏa mãn một số
điều kiện nào đó, và phương án đó là tốt nhất theo một tiêu chí cụ thể. Các bài
toán như vậy được gọi là bài toán tối ưu. Có nhiều bài toán tối ưu không có thuật
toán nào thực sự hữu hiệu để giải quyết, mà cho đến nay vẫn phải dựa trên mô
hình xem xét toàn bộ các phương án, rồi đánh giá để chọn ra phương án tốt nhất.
Phương pháp nhánh và cận là một dạng cải tiến của phương pháp quay lui, được
áp dụng để tìm nghiệm của bài toán tối ưu.

Giả sử nghiệm của bài toán có thể biểu diễn dưới dạng một véctơ (x1, x2, …, xn),
mỗi thành phần x i (i = 1, 2, …, n) được chọn ra từ tập S i. Mỗi nghiệm của bài toán X
= (x1, x2, …, xn) được xác định độ tốt bằng một hàm f(X) và mục tiêu cần tìm
nghiệm X để f(X) đạt giá trị nhỏ nhất (hoặc lớn nhất).

Tư tưởng của phương pháp nhánh và cận như sau: Giả sử đã xây dựng được k
thành phần của nghiệm (x1, x2, …, xk) và khi mở rộng nghiệm (x 1, x2, …, xk+1), nếu
biết rằng tất cả các nghiệm mở rộng của nó (x 1, x2, …, xk+1, …) đều không tốt bằng
nghiệm tốt nhất đã biết ở thời điểm trước đó, thì ta không cần mở rộng từ (x 1, x2,
…, xk) nữa. Như vậy, với phương pháp nhánh và cận, ta không phải duyệt toàn bộ
các phương án để tìm ra nghiệm tốt nhất mà bằng cách đánh giá các nghiệm mở
rộng, ta có thể cắt bỏ những nhánh không cần thiết, do đó việc tìm nghiệm tối ưu
sẽ nhanh hơn. Cái khó nhất trong việc áp dụng phương pháp nhánh và cận là
đánh giá các nghiệm mở rộng, nếu đánh giá được tốt sẽ giúp bỏ qua được nhiều
phương án không cần thiết, khi đó thuật toán nhánh cận sẽ chạy nhanh hơn nhiều
so với thuật toán vét cạn.
Thuật toán nhánh và cận có thể mô tả bằng mô hình đệ quy sau:

Procedure BranchBound(i); {xây dựng thành phần thứ i}

Begin
<đánh giá các nghiệm mở rộng>;
If <các nghiệm mở rộng đều không tốt hon BestSolution>
Then exit;
<xác định Si: tập các phương án có thể chọn cho x i>;
For xi
Si do
Begin
<ghi nhận thành phần thứ i>;
If i=n then <cập nhật lại BestSolution>
Else BranchBound(i+1);
<loai bo thành phần thứ i>;
End;
End;

Trong đó, BestSolution là nghiệm tốt nhất đã biết ở thời điểm đó. Thủ tục < cập
nhật lại BestSolution> sẽ xác định “độ tốt” của nghiệm mới tìm thấy, nếu nghiệm
Võ Văn Trị - CQB | Confidential


mới tìm thấy tốt hơn BestSolution thì BestSolution sẽ được cập nhật lại là nghiệm
mới tìm được.
1. Bài toán máy rút tiền tự động ATM
Bài toán

Một máy ATM hiện có n ( ≤ 20) tờ tiền có giá t1, t2, …, tn. Hãy tìm cách trả ít tờ
nhất với số tiền đúng bằng S.
Input: Tệp ATM.INP có dạng:
-


Dòng đầu là hai số n và S.
Dòng thứ hai gồm n số t 1, t2, …, tn.

Output: Tệp ATM.OUT có dạng: Nếu có thể trả tiền đúng bằng S thì đưa ra số tờ ít
nhất cần trả và đưa ra cách trả, nếu không ghi -1.
ATM.INP
10 390
200 10 20 20 50 50 50 50 100 100

ATM.OUT
5
20 20 50 100 200

Giải:

Như đã biết, nghiệm của bài toán là một dãy nhị phân có độ dài n, giả sử đã xây
dựng được k thành phần (x 1, x2, …,xk), đã trả được sum và sử dụng c tờ tiền. Để
đánh giá các nghiệm mở rộng của (x1, x2, …,xk) ta nhận thấy:
-

Còn phải trả S – sum;
Gọi tmax[k] là giá cao nhất trong các tờ tiền còn lại (tmax[k] = max{tk+1, …, tn})
Thì ít nhất cần sử dụng thêm [

Do đó, nếu c +



[ ]




] tờ nữa

[ ]

mà lớn hơn hoặc bằng số tờ của cách trả tốt nhất hiện có thì

không cần mở rộng các nghiệm của (x 1, x2, …, xk) nữa.
Chương trình:

{Giai bai toan ATM bang phuong phap nhanh can}
Program ATM_BranchandBound;
const
fi = 'atm.inp';
fo = 'atm.out';
nm = 20;
var
x, xbest:array[1..nm] of byte;
t, tmax:array[1..nm] of longint;
c, cbest:longint; n,s,sum:longint;
procedure docdl;
var
j:byte; f:text;

Võ Văn Trị - CQB | Confidential


Page 14 of 84
begin

assign(f,fi); reset(f);
readln(f,n,s);
for j:=1 to n do read(f,t[j]);
close(f);
end;
procedure init;
var
j:byte;
begin
tmax[n]:=t[n];
for j:=n-1 downto 1 do
begin
tmax[j]:=tmax[j+1];
if tmax[j]end;
sum:=0; c:=0;
cbest:=n+1;
end;
procedure update;
begin
if (sum = s) and (cbegin
xbest:=x; cbest:=c;
end;
end;
procedure BranchBound(i:byte);
var
j:byte;
begin
if c + (s - sum)/tmax[i] >=cbest then exit;

for j:=0 to 1 do
begin
x[i]:=j;
sum:=sum+x[i]*t[i];
c:=c+j;
if i=n then update
else
if sumsum:=sum-x[i]*t[i];
c:=c-j;
end;
end;
procedure ghinghiem;
var
j:byte; f:text;
begin
assign(f,fo);
rewrite(f);
if cbestbegin
writeln(f,cbest);
for j:=1 to n do

Võ Văn Trị - CQB | Confidential


if xbest[j]=1 then write(f,t[j],' ');
end
else writeln(f,-1);
close(f);

end;
begin
docdl;
init;
branchbound(1);
ghinghiem;
end.

2. Bài toán người du lịch
Bài toán: Cho n thành phố được đánh số từ 1 đến n và các tuyến đường giao
thông hai chiều giữa chúng, mạng lưới giao thông này được cho bởi mảng C[1..n,
1..n], ở đây Cịj = Cji là chi phí đi đoạn đường trực tiếp từ thành phố i đến thành
phố j (hoặc ngược lại).
Một người du lịch xuất phát từ thành phố 1, muốn đi thăm tất cả các thành phố
còn lại mỗi thành phố đúng 1 lần và cuối cùng quay lại thành phố 1. Hãy chỉ ra cho
người đó hành trình với chi phí ít nhất. Bài toán được gọi là bài toán người du lịch
hay bài toán người chào hàng (Travelling Salesman Problem – TSP).
Input: Tệp TSP.INP có dạng:
-

Dòng đầu chứa số n (1 ≤ ≤ 20), là số thành phố;
n dòng tiếp theo, mỗi dòng n số mô tả mảng C.

Output: Tệp TSP.OUT có dạng:
-

Dòng đầu là chi phí ít nhất;
Dòng thứ hai mô tả hành trình

Ví dụ 1


TSP.INP
4
0 20 35 42
20 0 34 30
35 34 0 12
42 30 12 0

TSP.OUT
97
1->2->4->3->1

Hình ảnh minh họa

Ví dụ 2

Võ Văn Trị - CQB | Confidential


Page 16 of 84
TSP.INP
4
0 20 35 10
20 0 90 50
35 90 0 12
10 50 12 0

TSP.OUT

Hình ảnh minh họa


117
1->2->4->3->1

Giải
1) Hành trình cần tìm có dạng (x 1 = 1, x2, …, xn, xn+1 = 1), ở đây giữa x i và xi+1:
hai thành phố liên tiếp trong hành trình phải có đường đi trực tiếp; trừ
thành phố 1, không thành phố nào được lặp lại hai lần, có nghĩa là dãy (x 1,
x2, …, xn) lập thành một hoán vị của (1, 2, …, n).
2) Duyệt quay lui: x 2 có thể chọn một trong các thành phố mà x 1 có đường đi
trực tiếp tới, với mỗi cách thử chọn x 2 như vậy thì x 3 có thể chọn một trong
các thành phố mà x 2 có đường đi tới (ngoài x 1). Tổng quát: xi có thể chọn 1
trong các thành phố chưa đi qua mà xi-1 có đường đi trực tiếp tới
(2 ≤ ≤ ).
3) Nhánh cận: Khởi tạo cấu hình BestSolution có chi phí bằng +∞. Với mỗi
bước thử chọn x i xem chi phí đường đi cho tới lúc đó có nhỏ hơn chi phí
của cấu hình BestSolution không? Nếu không nhỏ hơn thì thứ giá trị khác
ngay bởi có đi tiếp cũng chỉ tốn thêm. Khi thử được một giá trị x n ta kiểm
tra xem xn có đường đi trực tiếp về 1 hay không? Nếu có đánh giá chi phí đi
từ thành phố 1 đến thành phố x n cộng với chi phí từ x n trực tiếp về 1, nếu
nhỏ hơn BestSolution thì cập nhật lại BestSolution bằng cách đi mới.

Chương trình

{Giai bai toan nguoi du lich bang phuong phap nhanh can}
program TSP;
const
fi='tsp.inp';
fo='tsp.out';
nm=20;

oo=1000000;
var
c:array[1..nm,1..nm] of longint;
x,xbest:array[1..nm] of byte;
ok:array[1..nm] of boolean;
sum,best:longint; n:byte;
procedure docdl;
var
i,j:byte;

Võ Văn Trị - CQB | Confidential


f:text;
begin
assign(f,fi); reset(f);
readln(f,n);
for i:=1 to n do
for j:=1 to n do read(f,c[i,j]);
close(f);
end;
procedure update;
begin
if sum+c[x[n],x[1]]begin
best:=sum+c[x[n],x[1]];
xbest:=x;
end;
end;
procedure init;

begin
fillchar(ok[1],n,true);
ok[1]:=false;
x[1]:=1;
best:=oo;
end;
procedure branchbound(i:byte);
var
j:byte;
begin
if sum>best then exit;
for j:=1 to n do
if ok[j] and (c[x[i-1],j]<>0) then
begin
x[i]:=j;
ok[j]:=false;
sum:=sum+c[x[i-1],j];
if i=n then update
else branchbound(i+1);
sum:=sum-c[x[i-1],j];
ok[j]:=true;
end;
end;
procedure ghinghiem;
var
f:text; i:byte;
begin
assign(f,fo); rewrite(f);
writeln(f,best);
for i:=1 to n do write(f,xbest[i],'->');

writeln(f,xbest[1]);
close(f);
end;

Võ Văn Trị - CQB | Confidential


Page 18 of 84
begin
docdl;
init;
branchbound(2);
ghinghiem;
end.

Bài tập luyện tập
2.1 Bài tập 4.15 (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)
Lời giải bài toán người du lịch ở trên là một giải pháp nhánh cận rất thô sơ. Nếu
chạy chương trình với trường hợp: số thành phố n=20, khoảng cách giữa các
thành phố bằng 1 (nghĩa là c[i, j]=1 với i<>j). Khi đ ó chương trình không cho ra
được kết quả vì độ phức tạp rất lớn 20!, không thể chạy trong thời gian cho phép.
Ta nên cải tiến cách đánh giá như sau:

Để đi qua tất cả các thành phố, sau đó quay lại thành phố 1 ta phải đi qua n tuyến
đường (qua tổng cộng n+1 điểm, vì thành phố 1 được tính 2 lần). Giả sử ta đã xây
dựng hành trình đi qua i thành phố với tổng chi phí là sum, ta gọi min là chi phí
của cạnh nhỏ nhất trong các cạnh chưa đi qua. Nếu như sum + (n -i+1)*min
>=best thì ta không nên xây dựng tiếp hành tr ình từ thành x i nữa mà nên quay lui
để chọn hướng đi khác để hi vọng có được hành trình tốt hơn.
Để dễ dàng khi tìm min trong các cạnh chưa xét, ta xây dựng danh sách cạnh gồm

½ số cạnh nằm phía trên đường chéo chính (i2.2 Bài tập 4.16 (nguồn bài: Tài liệu giáo khoa chuyên tin Q1)

Cho bàn cờ quốc tế 8 x 8 ô, mỗi ô ghi một số nguyên dương không vượt quá
32000. Hãy xếp 8 quân hậu lên bàn cờ sao cho không có quân nào khống chế
được quân nào và tổng các số ghi trên các ô mà quân hậu đứng là lớn nhất.
Input: tệp gồm 8 dòng, mỗi dòng ghi 8 số nguyên dương, giữa các số cách nhau
một dấu cách.
Output: tệp có một số duy nhất là đáp số của bài toán.
1
6
3
2
1
2
2
8

2
9
6
3
2
1
1
2

4
5
2

7
3
3
3
3

Dữ
9 3
4 2
3 4
3 2
2 3
4 2
2 8
4 2

liệu vào
2 1 4
3 1 4
1 8 3
1 4 2
9 2 1
4 2 8
4 2 1
3 1 8

Kết quả ra

66


Giải

Võ Văn Trị - CQB | Confidential


Như đã biết, trên mỗi cột của bàn cờ chỉ đặt được một quân hậu. Để tìm nghiệm
bài toán, ta phải duyệt quay lui:
-

-

-

-

Tại bước thứ i, ta chọn một dòng j nào đấy để đặt quân hậu thứ i vào (với điều
kiện dòng j, đường chéo // với đường chéo chính, đường chéo // với đường
chéo phụ đi qua ô đó chưa được đặt một quân hậu nào).
Tuy nhiên, nếu duyệt tất cả các phương án sau đó tìm cách đặt tốt nhất trong
92 cách đặt thì không khả thi vì chươn g trình sẽ không đảm bảo về mặt thời
gian.
Giả sử tới bước thứ i, ta đã đặt được i quân hậu với tổng giá trị là sum. Gọi
max[k] là giá trị lớn nhất của các ô chưa bị kiểm soát trên các cột còn lại:
max[k] = max{dk+1, …, dn}

với d h là giá trị lớn nhất của các ô chưa bị kiểm soát trên cột h.
Khi đó, nếu mở rộng nghiệm theo phương án này thì giá trị lớn nhất có thể đạt
sum + max[k] * (n-i);
Do vậy, nếu sum + max[k]*(n-i)<= BestSolution thì không cần mở rộng nghiệm
theo hướng này nữa.


2.3 Xếp valy (nguồn bài: spoj)

Một va ly có thể chứa tối đa W đơn vị trọng lượng. Có N loại đồ vật, số lượng
mỗi loại không hạn chế. Loại đồ vật thứ i có trọng lượng Ai và có giá trị Ci. Hỏi
nên chọn những loại đồ vật nào và số lượng bao nhiêu để xếp vào va ly sao cho:
-

Tổng trọng lượng của các vật không vượt quá giới hạn W của valy;
Tổng giá trị của các vật là lớn nhất.

Giải: Với mỗi loại đồ vật i, gọi Ti là giá trị riêng của nó: Ti = Ci / Ai. Sau đó sắp các
loại đồ vật theo thứ tự giảm dần của Ti.

Phương pháp duyệt nhánh cận tại mỗi bước duyệt loại đồ vật i, ta lấy k đồ vật
loạinày, khi đó điều kiện để duyệt tiếp loại đồ vật i+1 (điều kiện nhánh cận) là:
-

k*Ai <= w
s(i) + k*Ci + (w - k*Ai)*Ti+1 > smax

Trong đó:
-

w là trọng lượng mà valy có thể chứa thêm sau bước duyệt loại đồ vật i-1;
s(i) là tổng giá trị hiện đang có của valy sau bước duyệt loại đồ vật i-1;
smax là tổng giá trị valy của một các xếp đã tìm được nhưng chưa tối ưu. Có
thể khởi tạo smax ban đầu bằng 0.

2. 4 Xâu ABC


Tìm một xâu chỉ gồm các ký tự A,B,C sao cho

Võ Văn Trị - CQB | Confidential


Page 20 of 84
-

Có độ dài N cho trước (N<=1000)
Hai đọan con bất kì liên nhau đều khác nhau (đoạn con là một dãy các ký tự
liên tiếp của xâu).
Có ít ký tự C nhất

Giải (thầy Lê Minh Hoàng):

Thuật toán 1: Ước lượng hàm cận

Ta sẽ dùng thuật toán quay lui để liệt kê các dãy n ký tự mà mỗi phần tử của dãy
được chọn trong tập {A, B, C}. Giả sử cấu hình cần liệt kê có dạng x[1..n] thì:
Nếu dãy x[1..n] thoả mãn 2 đoạn con bất kỳ liền nhau đều khác nhau, thì trong 4 ký
tự liên tiếp bất kỳ bao giờ cũng phải có ít nhất một ký tự ‘C’. Như vậy với một đoạn
gồm k ký tự liên tiếp của dãy x[1..n] thì số ký tự ‘C’ trong đoạn đó luôn ≥ [ ].
4

Sau khi thử chọn ∈ { , , , }, nếu ta đã có ký tự ‘C’ trong đoạn x[1..i], thì cho
dù các bước chọn tiếp sau làm tốt như thế nào chăng nữa, số ký tự ‘C’ phải chọn

thêm không bao giờ ít hơn [ ]. Tức là nếu theo phương án chọn như thế này thì
4




số ký tự ‘C’ trong dãy kết quả (khi chọn đến ) không thể ít hơn + [ ]. Ta dùng
4
con số này để đánh giá nhánh cận, nếu nó nhiều hơn số ký tự ‘C’ trong cấu hình tốt
nhất đang cho tới thời điểm hiện tại thì chắc chắn có làm tiếp cũng ch ỉ được một
cấu hình tồi tệ hơn, ta bỏ qua ngay cách chọn này và thử phương án khác.
Thuật toán này hoạt động khá nhanh với ≤ 100, tuy nhiên với những giá trị n lớn
thì thời gian thực hiện vẫn tương đối lâu. Dưới đây là một thuật toán tốt hơn, đi đôi
với nó là một chiến lược chọn hàm cận hiệu quả hơn.
Thuật toán 2: Lấy ngắn nuôi dài

Với mỗi độ dài m, ta gọi f[m] là số ký tự ‘C’ trong dãy thỏa hai đoạn con bất kỳ liền
nhau đều khác nhau và có ít ký tự C nhất. Rõ ràng f[0] = 0, ta lập trình tính các f[m]
khác với điều kiện là f[0..m-1] đã biết.
Tương tự như thuật toán 1, ta giả sử cấu hình cần tìm có dạng x[1..m] thì sau khi
thử chọn xi, nếu ta đã có t i ký tự ‘C’ trong đoạn x[1..i] thì dù cho các bước tiếp sau có
làm tốt thế nào đi chăng nữa, số ký tự ‘C’ chọn thêm sẽ không ít hơn f[m-i], tức là
nếu chọn tiếp thì số ký tự ‘C’ sẽ không ít hơn + [ − ]. Ta dùng cận này kết hợp
với thuật toán quay lui để tìm xâu tối ưu độ dài m.
2.5 Tour du lịch rẻ nhất (TOUR)

Một khu thắng cảnh gồm n điểm đánh số từ 1
tới n (n ≤ 100) và m đường đi hai chiều giữa
các cặp địa điểm đó, chi phí đi trên các đường
đi là biết trước ( ≤ 10000). Một Tour du lịch là
một hành trình xuất phát từ một địa điểm đi
thăm ≥ 2 địa điểm khác và quay trở về điểm
xuất phát, ngoại trừ địa điểm xuất phát, không

địa điểm nào bị thăm tới hai lần. Chi phí của một

Võ Văn Trị - CQB | Confidential


Tour du lịch là tổng chi phí các quãng đường đi qua.
Yêu cầu: Hãy tìm Tour du lịch có chi phí rẻ nhất.
Dữ liệu: TOUR.INP
-

Dòng 1: Ghi hai số nguyên dương n, m
m dòng tiếp theo mỗi dòng có dạng x y c. Cho biết có đường đi trực tiếp nối
địa điểm x với địa điểm y và chi phí đi quãng đường đó là c.

Kết quả: TOUR.OUT
-

-

Dòng 1: Ghi số 1 nếu như tồn tại hành trình theo yêu cầu, ghi số 0 nếu không
tồn tại hành trình.
Nếu dòng đầu tiên ghi số 1:
Dòng thứ 2 ghi chi phí của tour tìm được
Dòng thứ 3 ghi số k là số địa điểm tới thăm
Dòng thứ 4 gồm k số, số thứ i là địa điểm tới thăm thứ i trong tour, quy ước
địa điểm thăm đầu tiên là địa điểm xuất phát, địa điểm thăm thứ k (địa
điểm cuối cùng) là địa điểm mà từ đó quay trở lại điểm xuất phát để kết thúc
hành trình.
Các số trên một dòng của Input/Output File được ghi cách nhau ít nhất một dấu
cách.


Ví dụ:

Tour.inp
5 10
1 2 10
1 3 2
1 4 2
1 5 9
2 3 9
2 4 2
2 5 2
3 4 10
3 5 2
4 5 8

Tour.out
1
10
5
3 5 2 4 1

2.6 Robot cứu hỏa - VOI 2007 (QBROBOT)
Trên một mạng lưới giao thông có n nút, các nút được đánh số từ 1 đến n và giữa
hai nút bất kỳ có không quá một đường nối trực tiếp (đường nối trực tiếp là một
đường hai chiều). Ta gọi đường đi từ nút s đến nút t là một dãy các nút và các
đường nối trực tiếp có dạng:
s = u1, e1, u2,..., ui, ei, ui+1, ..., uk-1, ek-1, uk = t,

trong đó u1, u2, …, uk là các nút trong mạng lưới giao thông, ei là đường nối trực

tiếp giữa nút ui và ui+1 (không có nút uj nào xuất hiện nhiều hơn một lần trong dãy
trên, j = 1, 2, …, k).
Võ Văn Trị - CQB | Confidential


Page 22 of 84
Biết rằng mạng lưới giao thông được xét luôn có ít nhất một đường đi từ nút 1
đến nút n.

Một robot chứa đầy bình với w đơn vị năng lượng, cần đi từ trạm cứu hoả đặt tại
nút 1 đến nơi xảy ra hoả hoạn ở nút n, trong thời gian ít nhất có thể. Thời gian và
chi phí năng lượng để robot đi trên đường nối trực tiếp từ nút i đến nút j tương
ứng là tij và cij (1 ≤ i, j ≤ n). Robot ch ỉ có thể đi được trên đường nối trực tiếp từ
nút i đến nút j nếu năng lượng còn lại trong bình chứa không ít hơn cij (1 ≤ i, j ≤
n). Nếu robot đi đến một nút có trạm tiếp năng lượng (một nút có thể có hoặc
không có trạm tiếp năng lượng) thì nó tự động được nạp đầy năng lượng vào bình
chứa với thời gian nạp coi như không đáng kể.
Yêu cầu: Hãy xác đ ịnh giá trị w nhỏ nhất để robot đi được trên một đường đi từ
nút 1 đến nút n trong thời gian ít nhất.
Input
-

Dòng đầu tiên chứa một số nguyên dương n (2 ≤ n ≤ 500);
Dòng thứ hai chứa n số, trong đó số thứ j bằng 1 hoặc 0 tương ứng ở nút j
có hoặc không có trạm tiếp năng lượng (j = 1, 2, …, n);
Dòng thứ ba chứa số nguyên dương m (m ≤ 30000) là số đường nối trực
tiếp có trong mạng lưới giao thông;
Dòng thứ k trong số m dòng tiếp theo chứa 4 số nguyên dương i, j, tij, cij (tij,
cij ≤ 10000) mô tả đường nối trực tiếp từ nút i đến nút j, thời gian và chi
phí năng lượng tương ứng.

Hai số liên tiếp trên một dòng trong file dữ liệu cách nhau ít nhất một dấu
cách.

Output
-

Ghi ra số nguyên dương w tìm đư ợc.

Example
Input:
4
0110
5
1254
1343
1494
2441
3452

Output:
3

2. 7 ROADS (nguồn bài: spoj)
Có N thành phố 1..N nối bởi các con đường một chiều. Mỗi con đường có hai giá
trị: độ dài và chi phí phải trả để đi qua. Bob ở thành phố 1. Bạn hãy giúp Bob tìm
Võ Văn Trị - CQB | Confidential


đường đi ngắn nhất đến thành phố N, biết rằng Bob chỉ có số tiền có hạn là K mà
thôi.

Dữ liệu

Dòng đầu tiên ghi t là số test. Với mỗi test, dòng đầu ghi K (0 ≤ K ≤ 10000). Dòng
2 ghi N, 2 ≤ N ≤ 100. Dòng 3 ghi R, 1 ≤ R ≤ 10000 là số đường nối. Mỗi dòng trong
N dòng sau ghi 4 số nguyên S, D, L, T mô tả một con đường nối giữa S và D với độ
dài L ( 1 ≤ L ≤ 100) và chi phí T (0 ≤ T ≤ 100). Lưu ý có thể có nhiều con đường
nối giữa hai thành phố.
Kết quả

Với mỗi test, in ra độ dài đường đi ngắn nhất từ 1 đến N mà tổng chi phí không
quá K. Nếu không tồn tại, in ra -1.
Ví dụ

Input:
2
5
6
7
1 2 2 3
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2
0
4
4
1 4 5 2
1 2 1 0

2 3 1 1
3 4 1 0

Output:
11
-1

Võ Văn Trị - CQB | Confidential


Page 24 of 84

Chuyên đề 3. Duyệt ưu tiên
1. Mã đi tuần
Cho bàn cờ tổng quát nxn và một quân mã (n ≤100). Hãy chỉ ra một hành trình
của mã xuất phát từ ô (x,y), đi qua tất cả các ô còn lại của bàn cờ, mỗi ô đúng
một lần (luật đi của mã như luật cờ vua).
Gợi ý: Nếu xem mỗi ô của bàn cờ là một đỉnh của đồ thị thì bài toán này tương
đương với bài toán tìm đường đi Haminton (đường đi qua tât cả các đỉnh và
qua mỗi đỉnh đúng một lần). Thuật toán duy nhất cho bài toán này là duyệt đệ quy
quay lui.
Tại mỗi bước duyệt, giả sử ta đang ở đỉnh u, từ đỉnh u ta ưu tiên đi sang đỉnh
v có cạnh nối với u mà bậc của v là nhỏ nhất (định nghĩa bậc của một đỉnh v là số
đỉnh nối với đỉnh v đó mà chưa được thăm).

Xét ví dụ trên hình, con mã đang đã đi được 4 bước và đang ở ô (7,G). Từ ô
này con mã có thể nhảy đến 1 trong 3 ô tiếp theo là (6,E) - có bậc là 6; ô (5,F) có bậc là 7; hoặc ô (5,H) - có bậc là 3. Con mã sẽ ưu tiên nhảy ô có bậc nhỏ hơn
trước - là ô (5,H).

Sự ưu tiên trong phương pháp duyệt trên giúp chương trình chạy nhanh

hơn rất nhiều so với khi duyệt đơn thuần. Tuy nhiên với trường hợp bài toán
vô nghiệm (không có đường đi Haminton) thì chương trình cũng chạy rất lâu vì
phải duyệt qua hết toàn bộ không gian dữ liệu. Do đó cần dùng thêm biến chặn thời
gian để xử lý trường hợp này.
2. Cặp điểm gần nhất

Trên mặp phẳng tạo độ cho N điểm phân biệt (N≤20000). Tìm 2 điểm mà
khoảng cách giữa chúng là ngắn nhất.
Gợi ý: Nếu duyệt thông thường, thuật toán mất O(N2) và chạy rất lâu với N lớn.
Sự ưu tiên trong phép duyệt sau đây giúp thuật toán chạy trong thời gian cho
phép và cho kết quả chính xác với phần lớn các test.

Ta sắp xếp lại các điểm theo một tiêu chí nào đó có liên quan đến khỏang
Võ Văn Trị - CQB | Confidential