Professional Documents
Culture Documents
MỞ ĐẦU
1. Nguyên lí cộng
Ví dụ 1: Có 8 quyển sách và 6 quyển vở khác nhau. Hỏi có bao nhiêu cách chọn
1 quyển trong các quyển trên?
* Hd:
Có 8 cách chọn ra 1 quyển sách
Có 6 cách chọn ra 1 quyển vở
Với mỗi cách chọn 1 quyển sách không trùng với cách chọn bất kì một quyển vở
nào và ngược lại với mỗi cách chọn 1 quyển vở không trùng với cách chọn bất kì một
quyển sách nào
=>có 8 + 6 = 14 cách chọn 1 quyển trong số các quyển sách và vở đã cho
* Đơn giản
Giả sử có m cách chọn đối tượng x, có n cách chọn đối tượng y và mỗi cách
chọn đối tượng x không trùng với bất kỳ cách chọn đối tượng y nào và ngược lại mỗi
cách chọn đối tượng y không trùng với bất kỳ cách chọn đối tượng x nào thì sẽ có
m+n cách chọn 1 trong các đối tượng đã cho
* Nguyên lí cộng tổng quát
2. Nguyên lí nhân
Ví dụ 5: Để đi từ TP A đến TP B có 3 cách chọn phương tiện đi lại (ôtô, tàu hỏa,
máy bay), để đi từ TP B đến TP C có 4 cách chọn phương tiện đi lại (ôtô, tàu hỏa, tàu
thủy, máy bay). Hỏi có bao nhiêu cách chọn bộ phương tiện để đi từ TP A đến TP C
phải đi qua TP B?
* Hd:
Gọi x1 là phương tiện đi từ A đến B, x2 là phương tiện đi từ B đến C. Khi đó: Có
3 cách chọn đối tượng x1, với mỗi cách chọn đối tượng x1 có 4 cách chọn đối tượng x2
=>Số cách chọn bộ phương tiện để đi từ A B C đúng bằng số cách chọn
bộ 2 thành phần (x1, x2)
= 3 x 4 = 12 cách
* Nguyên lí nhân tổng quát
Có m1 cách chọn đối tượng x1
với mỗi cách chọn đối tượng x1, có m2 cách chọn đối tượng x2
với mỗi cách chọn bộ đối tượng (x1, x2), có m3 cách chọn đối tượng x3
…..
với mỗi cách chọn bộ đối tượng (x1, x2,…, xn-1), có mn cách chọn đối tượng xn
=> thì có m1 x m2 x m3 x … x mn cách chọn 1 bộ có n thành phần (x1,x2…xn)
Ví dụ 6: Một lớp có 34 học sinh. Hỏi có bao nhiêu cách chọn ra một ban cán sự
lớp gồm 1 lớp trưởng, 1 lớp phó, 1 bí thư.
Biết rằng mỗi học sinh được tham gia vào ban cán sự chỉ được nhận 1 nhiệm vụ
và bất kì học sịnh nào cũng có thể tham gia ban cán sự?
GV đưa kq = 34 X 33 x 32 = 35904
Ví dụ 7: Có bao nhiêu số có 3 chữ số khác nhau lấy từ tập {0,1,2,3,4,5,6,7,8,9}?
GV đưa kq = 10 x 9 x 8 = 720
Ví dụ 8: Cho |A| = m và |B| = n chứng minh |AxB| = m*n
Ví dụ 9: Sau khi thực hiện xong các câu lệnh sau, giá trị của K bằng bao nhiêu?
n1 := 10; n2 := 20; n3 := 30;
k:=0;
for i1:=1 to n1 do k := k+1
for i2:=1 to n2 do k := k+1
for i3:=1 to n3 do k:=k+1;
Ví dụ 10: Có bao nhiêu xâu nhị phân độ dài 8?
Ví dụ 11: Có bao nhiêu xâu nhị phân độ dài n?
GV đưa kq= 2n
Ví dụ 12: Có bao nhiêu tập con của một tập hợp có n phần tử A={a1, a2, .., an}?
* Hd ví dụ 12:
Xét xâu nhị phân có n bít x1,x2, … ,xn
Sự tương ứng 1-1 giữa 1 tập con của tập hợp A với 1 dãy nhị phân độ dài n.
Bởi vì:
nếu bít xi = 1 tương ứng với việc có mặt phần tử ai trong tập con
nếu bít xi = 0 tương ứng với việc không có mặt phần tử ai trong tập con
Do đó, số tập con của một tập hợp có n phần tử đúng bằng số xâu nhị phân độ
dài n = 2n
3. Nguyên lí bù trừ
Nếu một nhiệm vụ có 2 công việc, 2 công việc có thể thực hiện đồng thời
Thì số cách thực hiện nhiệm vụ gồm 2 công việc đó bằng tổng số cách làm mỗi
công việc trừ đi số cách làm đồng thời cả 2 công việc.
Ví dụ 13: |A| = m và |B| = n
|A+B| = m + n - |A B|
Ví dụ 14: Có bao nhiêu xâu nhị phân độ dài 8 thỏa mãn có bít đầu bằng 1 hoặc
có 2 bít cuối là 00?
* Hd ví dụ 14:
+ Xâu nhị phân độ dài 8 có bít đầu bằng 1 là 1 bộ có dạng: 1x2x3x4x5x6x7x8, với
xi=(0,1) ==> có 27 = 128 xâu thỏa mãn
+ Xâu nhị phân độ dài 8 có bít kết thúc bằng 00 là 1 bộ có dạng: x 1x2x3x4x5x600
=> có 26 = 62 xâu thỏa mãn
+ Xâu nhị phân độ dài 8 có bít vừa bắt đầu bằng 1, vừa kết thúc bằng 00 là 1 bộ
có dạng: 1x2x3x4x5x600, ==> có 25 = 32 xâu thỏa mãn
Vậy số xâu thỏa mãn đề bài là 128 + 64 – 32 = 160.
* Trường hợp đơn giản: Cho 2 tập hợp A và B (chưa biết có rời nhau hay
không).
Khi đó: N(A B) = N(A) + N(B) - N(A B)
* Trường hợp tổng quát: Cho m tập hữu hạn A1, A2, ..,Am. Khi đó:
N(A1A2A3...Am) = N1-N2+N3-....+(-1)m-1Nm
Trong đó Nk là tổng phần tử của tất cả các giao của k_tập (tập con k phần tử) lấy
từ m-tập đã cho, với k=2,3,4, ..., m-1
Đặc biệt:
N1 = N(A1) + N(A2) + ...+ N(Am)
Nm = N(A1A2 ...Am)
Ví dụ 17: Cho 3 tập hợp A, B, C. biết:
A = {0,1,2,7,11,14,25}
B = {-5,-4,0,2,7,13,15,16,19}
C = {-5,-2,2,7,13,14,16,20,21}
Hỏi N(A B C) = ?
* Hd: Ta có N(A) = 7; N(B)=9; N(C)=9;
N1=7+9+9 =25
N2=N(AB)+N(AC)+N(BC) = 3 + 3+5=11
N3 = N(ABC) = 2
Theo nguyên lí bù trừ ta có:
N(A B C)=N1-N2+N3 = 25-11+2=16
Ví dụ 18: Hỏi trong tập X={1,2,...,10000} có bao nhiêu số không chia hết cho
bất cứ số nào trong các số 3,4,7?
* Bài tập tham khảo
BT1: Từ các số 0, 1, 2, .., 6 lập được bao nhiêu số tự nhiên chẵn có 3 chữ số?
BT2: Có bao nhiêu số có 2 chữ số mà chữ số hang chục khác 0 và số chẵn?
Bt3: Có bao nhiêu số tự nhiên có 5 chữ số trong đó số cách đều 2 số ở giữa là
giống nhau? (vd 32123)
BT4: Có bao nhiêu số tự nhiên có 6 chữ số chia hết cho 5?
4. Nguyên lí Dirichlê
“Cần nhốt 6 thỏ vào 5 lồng thì ắt phải có 1 lồng chứa ít nhất 2 thỏ”
* TQ: “Có n đồ vật được đặt vào k hộp, ắt phải có ít nhất 1 hộp chứa ít nhất [n /
k] đồ vật”
Ví dụ 15: Có 100 người. Hãy tìm xem có ít nhất bao nhiêu người có cùng tháng
sinh?
TL: [100/12] = 9
Ví dụ 16: Trong số 367 người, có bao nhiêu người có cùng ngày sinh?
Tl: 1
1. Hoán vị
a. Định nghĩa: Một hoán vị của n phần tử (n >= 1) là một cách sắp thứ tự n
phần tử đó.
Ví dụ1: Cho n=3 và các phần tử là {1,2,3} thì các hoán vị của 3 phần tử trên là:
(1,2,3), (1,3,2), (2,1,3), (2,3,1), (3,1,2), (3,2,1)
Ví dụ 2: Có 5 người xếp hàng, có chiều cao bằng nhau. Hỏi có bao nhiêu cách
xếp 5 người đó thành hàng?
*Tổng quát: Cho tập hợp A có n phần tử a1, a2, …,an. Một hoán vị của n phần tử
của tập hợp A là một bộ có n thành phần (x1, x2, …,xn), xi A và xi <>xj
i,j=1,2,..,n, i<> j
Một tổ hợp chập k của n phần tử của tập hợp A là một tập con gồm k thành phần
(x1, x2, …,xk), xi A, i= 1,2, .., k và x1 < x2 < x3 < ... , xk
n!
b)Số tổ hợp chập k của n phần tử kí hiệu là C kn : C kn =
k!(n k)!
Tư tưởng: Xây dựng số chỉnh hợp không lặp chập k của n phần tử bằng phương
pháp phân hoạch.
B1: Từ tập n phần tử đã cho ta lấy ra tất cả các tập con k phần tử (x1,x2…xk)
==> Có C kn cách chọn ra 1 tập con k phần tử từ tập n phần tử.
B2: Với mỗi tập con k phần tử, ta sắp thứ tự k phần tử của tập con đó ==> có k!
cách sắp thứ tự.
Mỗi cách sắp thứ tự của k phần tử đó là một chỉnh hợp không lặp chập k của n
phần tử đã cho.
=> theo nguyên lí nhân có A kn = C kn . k!
Thật vậy:
Trước tiên, ta xét tập các chỉnh hợp không lặp chập k của n phần tử.
Ta chia tập đó thành các lớp sao cho các chỉnh hợp không lặp chập k của n
phần tử trong cùng một lớp giống nhau về các phần tử nhưng khác nhau ở thứ tự
sắp xếp (Tức là, các chỉnh hợp không lặp chập k của n của 1 lớp là các hoán vị
của cùng 1 tập con).
- Ví dụ: Tập các chỉnh hợp không lặp chập 3 của. 4 phần tử {1,2,3,4} được chia
thành 4 lớp như sau:
Lớp 1: (1,2,3), (1,3,2), (2,1,3),(2,3,1),(3,1,2),(3,2,1)
Lớp 2: (1,2,4), (1,4,2), (2,1,4),(2,4,1),(4,1,2),(4,2,1)
Lớp 3: (1,3,4), (1,4,3),(3,1,4),(3,4,1),(4,1,3),(4,3,1)
Lớp 4: (2,3,4), (2,4,3),(3,2,4),(3,4,2),(4,2,3),(4,3,2)
- Ta nhận số lớp đúng bằng số tập con k phần tử của tập n phần tử đã cho (số tổ
hợp chập k của n). Với mỗi lớp lại có k! chỉnh hợp không lặp chập k của n phần tử.
Nếu ta kí hiệu C kn là số tổ hợp chập k của n thì theo nguyên lí nhân ta có:
A kn n!
A kn = C kn . k!, => C kn = = (ĐPCM)
k! k!(n k)!
Ví dụ 8: Có n đội bóng thi đấu vòng tròn sao cho cứ 2 đội bất kì gặp nhau đúng
một trận. Hỏi phải tổ chức bao nhiêu trận đấu?
* Hd ví dụ 8:
Cứ 2 đội thì có 1 trận đấu. Suy ra số trận đấu bằng số cách chọn 2 đội từ n đội,
n.(n 1)
bằng C n2 =
2
Ví dụ 9: Hỏi có bao nhiêu giao điểm của các đường chéo của một đa giác lồi n
đỉnh (n > 4) nằm trong đa giác. Giả thiết rằng không có ba đường chéo nào đồng quy
tại điểm ở trong đa giác.
* Hd ví dụ 9:
Cứ 4 đỉnh của đa giác, có một giao điểm của 2 đường chéo nằm trong đa giác.
Vậy số giao điểm của các đường chéo nằm trong đa giác đúng bằng số cách lấy
ra 4 đỉnh từ tập n đỉnh của đa giác lồi đã cho.
n! n.(n 1).(n 2)(n 3)
=> Số giao điểm cần đếm là: C n4 = =
4!.(n 4)! 24
c) Một vài tính chất quan trọng của các hệ số tổ hợp
+ Tính đối xứng: C kn = C n nk
+ Điều kiện đầu: C n1 = C nn =1
+ Công thức đệ quy: C kn = C kn 11 + C n k1 với n.k>0
n
+ Tổng tất cả các tập con của tập n phần tử C
k 0
k
n = 2n
II. Áp dụng phương pháp sinh vào giải một số bài toán
Program Bai1;
var n, i: integer;
b: Array[1..20] of 0..1;
count : word;
stop : boolean;
Procedure init;
var i: integer;
Begin
Write('Do dai day nhi phan = '); readln(n);
for i := 1 to n do b[i] := 0;
stop := false;
count := 0;
End;
Procedure Next_Bit_String;
Var i: integer;
Begin
i:=n;
while (i>=1) and (b[i]=1) do
Begin
b[i] := 0;
i := i-1;
End;
if i < 1 then stop := True Else b[i] := 1;
End;
BEGIN
init;
While not (stop) do
Begin
count := count + 1;
Write(Count:5);
For i := 1 to n do Write(b[i]:2); writeln;
Next_Bit_String;
End;
Write('Bam phim Enter de thoat khoi Chuong trinh '); Readln;
END.
2. Bài toán 2: Liệt kê các tập con m phần tử của tập n phần tử.
Bài toán có thể phát biểu như sau: Cho tập hợp Cho X = {1, 2, 3, .. , n}. Hãy liệt
kê các tập con có m phần tử của X. Ví dụ: X= {1, 2, 3, 4, 5}, m= 3. Các tập con 3
phần tử của X là:
Procedure init;
Var i : integer;
Begin
Write('Moi nhap N '); Readln(n);
Write('Moi nhap M '); Readln(m);
For i:= 1 to m do a[i] := i;
stop := false;
count := 0;
End;
Procedure Next_Combination;
Var i, j: integer;
Begin
i := m;
while (i>0) and (a[i] = n-m+i) do i := i-1;
if i = 0 then stop := True
else
begin
a[i] := a[i]+1;
for j := i+1 to m do a[j] := a[i]+j-i;
end;
End;
BEGIN
init;
while not stop do
begin
count := count+1;
Write(count:5);
for i := 1 to m do write(a[i]:3); writeln;
Next_Combination;
end;
1. (1, 2, 3) 4. (2, 3, 1)
2. (1, 3, 2) 5. (3, 1, 2)
3. (2, 1, 3) 6. (3, 2, 1)
Phân tích bài toán:
Ta thấy ngay theo thứ tự trên thì cầu hình đầu tiên là: (1, 2, 3, . . n) và cấu hình
cuối cùng là (n, n-1, ..., 2, 1).
Cần xây dựng thuật toán để từ cấu hình đang có là (a1, a2, …, an) ta tìm được cấu
hình tiếp theo. Từ cấu hình đang có {a1, a2, …, an} ta xây dựng cấu hình tiếp theo
bằng qui tắc:
– Tìm j đầu tiên thoả mãn aj<aj+1 (j giảm từ n, n-1, …, 1);
– Tìm ak là số nhỏ nhất và ak>aj trong các số aj+1, aj+2, …, an;
– Đỗi chỗ aj với ak;
– Đảo ngược đoạn từ aj+1 đến an.
Ví dụ với n = 6, cấu hình đang có là (3, 6, 2, 5, 4, 1)
+ Ta tìm được j = 3
+ Ta tìm được k = 5
+ Hoán vị a3 với a6 ta được (3, 6, 4, 5, 2, 1)
+ Đảo ngược đoạn a4, a5, a6 ta được (3, 6, 4, 1, 2, 5)
Cấu hình tiếp theo là (3, 6, 4, 1, 2, 5)
Chương trình của bài toán là:
Program hoan_vi;
Var a:array[1..100] of integer;
n,i: integer;
count: longint;
stop : Boolean;
Procedure init;
var i: integer;
Begin
Write('Moi nhap vao N '); Readln(n);
For i := 1 to N do a[i]:=i;
Stop := False;
Count := 0;
end;
Procedure next_Permution;
Var j,k,l,m:integer;
tg: integer;
Begin
j := n-1;
while (j >0) and (a[j] > a[j+1]) do dec(j);
if j = 0 then stop := True else
begin
k := n;
while a[j] > a[k] do dec(k);
tg := a[j];
a[j] := a[k];
a[k] := tg;
l := n;
m := j+1;
while l > m do
begin
tg := a[l];
a[l] := a[m];
a[m] := tg;
dec(l);
inc(m);
end;
end;
end;
BEGIN
init;
while not stop do
begin
inc(count); write(count,'. ');
For i := 1 to N do write(a[i]:3); writeln;
next_permution;
end;
Write('Bam Enter de ket thuc '); Readln;
END.
4. Bài toán 4: Phân tích số nguyên dương N thành tổng các số nguyên không âm.
Bài toán được phát biểu như sau: Cho một số nguyên dương N. Hãy liệt kê các
cách phân tích N thành tổng các số nguyên không âm. Ví dụ với N = 4, ta có thể phân
tích thành:
1. 4
2. 3 + 1
3. 2 + 2
4. 2 + 1 + 1
5. 1 + 1 + 1 + 1
Phân tích bài toán:
Ta có thể thấy ngay cấu hình đầu tiên là: N và cấu hình cuối cùng là 1 1 1 … 1
(N chữ số 1).
Cần xây dựng thuật toán để từ cấu hình ta tìm được cấu hình tiếp theo. Từ cấu
hình a1, a2, …, ak ta có thể xây dựng cấu hình tiếp theo với quy tắc sau:
- Tìm i đầu tiên sao cho ai 1 (i giảm từ k, k-1, … 1)
- Thay ai = ai -1
- Phân tích (k-i+1) thành các số như sư:
+ Gán aj :=ai với j từ i+1 đến i + [(i+ k-i+1) div ai ];
writeln;
End;
Procedure Next_Division;
Var i, j, r, s, d : integer;
Begin
i := k;
while (i>0) and (c[i] =1) do dec(i);
if i>0 then
begin
c[i] := c[i]-1;
d := k-i+1;
r := d div c[i];
s := d mod c[i];
k := i;
if r>0 then
begin
for j := i+1 to i+R do c[j]:=c[i];
k := k+r;
end;
if s>0 then
begin
k := k+1;
c[k] := s;
end;
end else stop := True;
End;
Procedure Division;
Var i: integer;
Begin
While not stop do
begin
result;
next_division;
end;
Write('Bam Enter de ket thuc '); Readln
End;
BEGIN
init;
division;
END.
Như vậy, với mỗi bài toán như trên, khi sử dụng phương pháp sinh vào giải
quyết chúng ta thường gặp khó khăn như sau: một là việc xác định cấu hình đầu tiên
và cấu hình cuối cùng, hai là việc xác định được quy tắc để từ cấu hình đang có ta tìm
ra được cấu hình tiếp theo (sinh ra cấu hình tiếp theo). Chính vì nguyên nhân đó mà
phương pháp sinh không được sử dụng rộng rãi vào giải quyết các bài toán như thuật
toán quay lui, quy hoạch động, …Phương pháp sinh chỉ được sử dụng vào giải quyết
một số bài toán liệt kê các cấu hình tổ hợp đơn giản.
Hình H1.
Gọi D là tập hợp các phần tử từ 1 đến n (n=3), trong tập hợp D=(1,2,3) lần lượt
lấy các giá trị X(x1, x2, x3) để được một hoán vị.
Ví dụ: HV(1,2,3); HV(2,3,1); HV(1,3,2)...
Ban đầu trong D, các giá trị 1, 2 và 3 đều sẵn sàng.
– Bước 1, D(1,2,3) ta chọn x1=1, đánh dấu đã chọn 1 trong còn D(2,3).
– Bước 2, D(2,3) chọn x2=2, đánh dấu đã chọn 2 hay D(3).
– Bước 3, D(3) chọn x3=3, đánh dấu đã chọn 3 hay D().
Xét thấy dãy X thoả mãn là một hoán vị X=123 (chọn đủ n=3 giá trị x1, x2 và
x3), ghi nhận hoán vị này. Tiếp tục tìm kiếm cách thay thế giá trị của các xi
(i=1..3) để được hoán vị khác.
Thông thường và để tránh trường hợp bỏ xót nghiệm ta thường bắt đầu xét lại
theo quy luật nào đó. Trong bài toán này, ta bắt đầu từ x3 tr ở về trước. Chọn giá
trị khác 3 trong D cho x3, giải phóng phóng 3. Xét thấy trong D không còn giá trị
nào thoả mãn. Vậy ta phải xem xét lại từ x2 (tại Bước 2).
Tất nhiên, khi trở lại bước 2. Phần tử đang xét là x2, có nghĩa là x3 chưa nhận
giá trị nào và x2 cũng không nhận lại giá trị 2 nữa vì đã nhận trước đó rồi (để
tránh trùng nghiệm và mất thời gian)... Từ hình vẽ trong ví dụ này là ta trực tiệp
nhìn thấy. Nhưng với số lượng các phần tử trong D nhiều và rất nhiều, đồng thời
với máy tính không thể suy luận và nhìn thấy theo cách này mà phải dùng kĩ
thuật đánh dấu thông qua các biến hoặc kĩ xảo lập trình để ghi nhận các giá trị đã
hoặc chưa xét.
– (Bước 2.2)Tại đây ta chọn giá trị khác cho x2 trong D, chọn x2=3 thoả mãn.
– (Bước 2.3) Chọn một giá trị cho x3 trong D, được x3=2 thoả mãn.
Xét thấy dãy X thoả mãn là một hoán vị X=132, để tìm các hoán vị khác ta lại
bắt đầu từ x3 ngược trở lại, tìm một giá trị trong D không lặp lại và chưa được
chọn.
Lại tìm giá trị khác cho x3, không tìm thấy giá trị nào trong D thoả mãn, quay
lại Bước 2.2 (Bước 2), tại đây cũng không tìm được giá trị nào thoả mãn cho x2.
Tiếp tục quay về bước trước đó là Bước 1 để xác định lại x1.
– (Bước 1.2) Ta chọn x1=2, trong còn D(1,3). Tiếp theo ta chọn x2=1, trong còn
D(3) và cuối cùng chọn x3=3. Thoả mãn là một hoán vị X=213. Tiếp tục tìm các hoán
vị khác, lại quay ngược trở lại cho đến khi tìm được giá trị thoả mãn cho x i nào đó.
– Cứ như vậy cho đến khi không còn quay lui được nữa. Thuật toán kết thúc và ta
tìm xong các nghiệm của bài toán.
Ví dụ 2: Cho bàn cờ vua NxN, hãy tìm tất cả các cách đặt N quân hậu lên bàn cờ
sao cho không quân nào ăn được quân nào.
H1-1
Một lời giả của bài toán khi n=4. Hình H1-1.
Ta nhận thấy rằng mỗi quân hậu sẽ được đặt trên một hàng, nên ta gọi quân hậu
đặt ở hàng 1 là hậu 1, quân hậu đặt ở hàng 2 gọi là hậu 2… quân hậu ở hàng n gọi là
quân hậu n. Vấn đề là tìm xem hậu i được đặt tại cột j nào (j=1..n) sao cho không hậu
nào ăn được hậu nào.
* Hd:
– Đặt tất cả các hậu tại đầu mỗi hàng như hình H1-2, sau đó lần lượt đặt các quân
hậu từ 1 đến n vào các cột tương ứng từ 1 đến n sao cho không hậu nào ăn được nhau.
– Đặt hậu 1- vào ô 11, đặt hậu 2- vào ô 23 (ô 21 và 22 bị hậu 1 ăn được), tại dòng
3 các ô đều bị hậu 1 hoặc hậu 2 không chế, vậy đi theo cách này không được. quan sát
hình ảnh sau:
Các vị trí đánh dấu
(X) bị hậu đặt trước đó
khống chế
H1-3
– Do vậy ta phải đặt lại hậu 2.
dấu “chấm đậm”
thể hiện vị trí đã
được thử đặt
trước đó
H1-4
– Đặt hậu 2 vào ô 24, hậu 3 vào ô 32, -> không có cách đặt hậu 4, đi theo hướng
này không có lời giải. Nên ta chọn lại cách đặt hậu 3, từ hình H1-4 thấy rằng không
còn cách đặt khác cho hậu 3, nên ta quay lại và thử đạt lại hậu 2 một lần nữa, từ hình
H1-4 thấy rằng không còn cách đặt hậu 2. Ta tiếp tục quay lại tìm cách đặt lại hậu 1.
Còn với cách đặt hậu 1 tại ô 11 thì sẽ không tìm thấy lời giải. Bây giờ ta đặt hậu 1
sang ô 22 và tiếp tục với các hậu khác ta có kết quả như sau:
H1-5
– Từ bàn cờ cuối trong hình H1-5 ta thấy đây là một lời giải của bài toán, ghi nhận
nghiệm này và tiếp tục tìm lời giải khác.
– Lần lượt đặt lại các vị trí của các hậu, bắt đầu từ hậu 4 quay dần trở lại.
– Xét hậu 4: còn ô 44 nhưng lại bị hậu 2 không chế, nên ta quay lại hậu 3.
– Xét hậu 3: các ô 32, 33 và 34 bị hậu 1 hoặc 2 không chế. Quay lại hậu 2.
– Xét hậu 2: hết ô có khả năng đặt hậu, quay lại hậu 1.
– Xét hậu 1: đặt vào ô 13; hậu 2 đặt vào ô 21; ô 31, 32 và 33 không thoả mãn nên
đặt hậu 3 vào ô 34 và cuối cùng đặt hậu 4 vào ô 42. Ta thu được nghiệm của bài toán,
quan sát hình ảnh dưới đây:
Hình H1-6.
– Bằng cách thử lại tương tự để tìm lời giải khác cho bài toán. Xét hậu 4, hậu 3 và
hậu 2 đều không tìm được ô khác. Trở lại từ hậu 1 đặt tại ô 14… (đi theo hướng này
cũng không có lời giải - người đọc tự làm tiếp).
– Kết luận: với n=4 tồn tại hai nghiệm (2,4,1,3) và (3,1,4,2)
Tóm lại, từ các ví dụ trên cho ta thấy, nghiệm của bài toán có dạng X(x1,x2…xn)
và để giải bài toán ta thường dựa vào hai điều kiện:
– Điều kiện 1(P) để xây dựng phần tử xi: ta lần lượt đưa ra các giá trị đề cử cho xi
và kiểm tra xem các giá trị này có thoả mãn hay không. Nếu được, tiếp tục với xi+1;
nếu không, có nghĩa là đi theo hướng này không tìm được nghiệm. Ta phải quay lại
và xét lại phần tử xi-1 với một giá trị đề cử khác. Hành động này gọi là quay lui.
– Điều kiện 2(Q) để kiểm tra xem các thành phần từ x1 đến xi đã là nghiệm của
bài toán hay chưa. Nếu thoả mãn thì ghi nhận nghiệm và dừng chương trình hoặc lại
lần lượt xét lại các xi, xi-1... để tìm nghiệm khác của bài toán.
Bước 2: Nếu X thoả Q dừng thuật toán, thông báo tìm được nghiệm X. Nếu
không thoả Q chuyển sang bước 3.
Bước 3: Xét tiết thành phần xi+1 để bổ sung cho X: (x1…xi+1) thoả P. Xảy ra
trường hợp sau đây:
Tìm được xi+1, quay lại Bước 2.
Không, tức là với mọi xi+1 thì (x1…xi+1) không thoả P, có nghĩa là nghiệm X
theo hướng (x1…xi) đã xét sẽ không đi đến kết quả, do vậy ta phải chọn một giá
trị khác trong D cho xi để làm được điều này ta giảm i một đơn vị rồi quay về
Bước 3.
Cách làm như trên gọi là quay lui một bước để tìm nghiệm của bài toán theo
hướng khác. (thuật toán, giả thuật, tìm kiếm + quay lui hoặc backtracking).
Để đảm bảo cho việc vét cạn mọi khả năng có thể có, các giá trị đề cử không
được bỏ sót. Để đảm bảo việc xét không bị trùng lặp, khi quay lui để xác định lại các
xi cần không thử lại các giá trị đã thử rồi. Lúc này ta cần kết hợp kỹ thuật đánh dấu
hợp lý các giá trị đã thử trước đó và khi quay lui ta cần trả về trạng thái cũ.
Từ ý tưởng trên ta đưa ra hai sơ đồ giải bài toán quay lui tìm nghiệm không đệ
quy như sau:
Khởi tạo X, X thoả P Khởi tạo X, X thoả P
Repeat Repeat
If X thoả Q then If X thoả Q then
Begin Begin
Ghi nhận nghiệm; Ghi nhận nghiệm;
Exit; Exit;
[Lùi; tìm các nghiệm;] [Lùi; tìm các nghiệm;]
End; End;
If Tìm một nước đi Then Tiến If Tìm một nước đi Then Tiến
Else Lùi; Else Lùi;
Until False; Until False;
If không nghiệm Then If không nghiệm Then
Else Begin Begin
Ghi nhận vô nghiệm; Ghi nhận vô nghiệm;
Exit; Exit;
End; End;
– Dưới đây là cách thể hiện dùng đệ quy tìm thành phần thứ i của nghiệm:
trong đó, mỗi x[i] có thể lấy một trong các giá trị từ 1 đến n. Theo định nghĩa
chỉnh hợp lặp, các giá trị này là độc lập với nhau, vì thế việc đề cử bất kỳ giá trị nào
từ 1 đến n cho x[i] đều mặc nhiên được chấp nhận. Các chỉnh hợp chập 3 (k=3) của 5
(n=5) giá trị 1,2,3,4,5 gồm nk = 53 =125 bộ nghiệm.
– Điều kiện chấp nhận : Điều kiện chấp nhận một giá trị đề cử được tính toán
trong thân vòng lặp của thủ tục đệ quy Try, vì thế tiết kiệm được một phép toán trong
biểu thức này là tiết kiệm được vô số các phép toán thực sự phải làm. Thông thường,
điều kiện chấp nhận tính tại bước i phụ thuộc vào cả những bước trước, vì thế để tiết
kiệm số phép toán, người ta thường dùng một số kỹ thuật sau:
Tổ chức những biến trạng thái để lưu trữ những thông tin cần thiết. Thay vì
phải tính lại, ta có thể truy xuất các giá trị của những biến này. Việc dùng một số
biến trạng thái, tuy phải trả giá bằng một số vùng nhớ nhất định nhưng tiết kiệm
được nhiều thời gian tính. Việc tổ chức hợp lý các biến trạng thái cũng là một kỹ
thuật làm tăng hiệu quả của việc duyệt.
Trong những tình huống phải tính lại, cố gắng kế thừa kết quả của những bước
trước bằng cách tổ chức thêm những tham số cho thủ tục đệ quy Try để truyền
cho những bước kế tiếp.
Nói chung, việc đưa ra được một biểu thức đơn giản để tính điều kiện chấp
nhận được là kết quả của việc phân tích kỹ lưỡng bài toán cũng như tổ chức tốt
dữ liệu. Mặt khác, điều kiện này được tính càng sát càng tốt, nó giúp ta lùi sớm
hơn (tiết kiệm thời gian hơn) trong quá trình tìm kiếm.
– Ghi nhận trạng thái mới và trả lại trạng thái cũ: tuỳ theo từng bài cụ thể.
3. Các ví dụ
(Các chương trình trong phần này được viết trên môi trường Free Pascal)
không cần quan tâm tới giá trị nào đã được chọn hay chưa. Ví dụ, các chỉnh hợp chập
3 (k=3) của 5 (n=5) giá trị 1,2,3,4,5 gồm nk = 53 =125 bộ nghiệm.
Cài đặt chương trình:
– Input: tệp Chlap.inp chứa hai số nguyên dương n và k.
– Output: tệp Chlap.out nhiều dòng, mỗi dòng chứa 1 số nguyên (là số tt chỉnh hợp
lặp) và 1 cấu hình chỉnh hợp lặp.
Chlap.inp Chlap.out
1: 1 1
32 2: 1 2
3: 1 3
4: 2 1
5: 2 2
6: 2 3
7: 3 1
8: 3 2
9: 3 3
Begin
d:=d+1;write(f,d:4,': ');
for i:=1 to k do write(f,' ',x[i]); writeln(f);
End;
procedure Try(i:integer);
Var j:integer;
Begin
for j:=1 to n do
if b[j] then
begin
x[i]:=j;
if i=k then Inkq
else try(i+1);
end;
End;
BEGIN
Init;
try(1);
close(f);
END.
Ta nhận thấy rằng, mỗi chỉnh hợp không lặp cũng có dạng X(x1, x2…xk). Các giá
trị đề cử của xi (i=1..k) là từ 1..n. Tuy nhiên các thành phần là đôi một khác nhau (xi
<> xj: i<>j, i,j=1..k). Do vậy ta phải sử dụng một biến mảng B để quản lý các giá trị
từ 1 đến n. nếu b[j] = true, chưa xét đến và ngược lại.
Cài đặt chương trình:
Input: tệp Chklap.inp chứa hai số nguyên dương n và k.
Output: tệp Chklap.out nhiều dòng, mỗi dòng chứa 1 số nguyên (là số tt chỉnh
hợp lặp) và 1 cấu hình chỉnh hợp lặp.
Chklap.inp Chklap.out
1: 1 2
32 2: 1 3
3: 2 1
4: 2 3
5: 3 1
6: 3 2
{CHINH HOP KHONG LAP CHAP K CUA N}
uses crt;
const MaxN=100;
fi='chklap.inp';{chua so N va K}
fo='chklap.out';{moi dong chua mot cau hinh}
var
x:array[1..MaxN] of Integer;
b:array[1..MaxN] of boolean;
n,k,d:integer;
f:text;
procedure Init;
var i:integer;
Begin
assign(f,fi);reset(f);
read(f,n,k);
close(f);
for i:=1 to n do b[i]:=true;
assign(f,fo);rewrite(f);
End;
Procedure Inkq;
var i:integer;
Begin
d:=d+1;write(f,d:4,': ');
for i:=1 to k do write(f,' ',x[i]); writeln(f);
End;
procedure Try(i:integer);
Var j:integer;
Begin
for j:=1 to n do
if b[j] then
begin
x[i]:=j;
b[j]:=false;
if i=k then Inkq
else try(i+1);
b[j]:=true;
end;
End;
BEGIN
Init;
Try(1);
close(f);
END.
Để liệt kê toàn bộ chỉnh hợp không lặp chập k, k=1..N. của các tập con
từ 1..N thì trong chương trình chính ta viết:
BEGIN
Init;
for k:=1 to n do Try(1);
close(f);
END.
c. Hoán vị
Phát biểu: Liệt kê các hoán vị của dãy số nguyên từ 1..N
Vd: Xem mục 1 phần Giải thuật quay lui.
Dùng mảng X để biểu diễn các hoán vị dạng x1x2…xn, trong đó với mỗi xi có thể
nhận một trong các giá trị từ 1..N. Vì mỗi giá trị j=1..n chỉ xuất hiện một lần trong dãy
X, nên ta dùng một mảng B để đánh dấu các giá trị, bj=true nếu j chưa được dùng. Áp
dụng thuật toán đệ quy quay lui để tìm thành phần xi như sau:
Procedure Try(i:integer);
Var j:integer;
Begin
for j:=1 to n do {các khả năng mà xi có thể nhận}
if b[j] then {nếu j chưa được dùng (thoả P)}
begin
x[i]:=j;
if i=n then PrintResult {thoả Q}
else begin
b[j]:=false; {đánh dấu đã dùng}
Try(i+1);
b[j]:=true; {trả lại trạng thái cũ - giải phóng}
end
end;
End;
Yêu cầu về dữ liệu:
Dữ liệu vào: tệp văn bản Hoanvi.inp chứa số nguyên N
Kết quả: ghi vào tệp Hoanvi.out mỗi dòng chứa một hoán vị
Chương trình được viết như sau:
Const
MaxN=100;
fi='hoanvi.inp';{chua so N}
fo='hoanvi.out';{moi dong chua mot hoan vi}
Var
x:array[1..MaxN] of integer;
b:array[1..MaxN] of boolean;
n:integer;
f:text;
procedure Init;
var i:integer;
Begin
assign(f,fi);reset(f);
read(f,n);
for i:=1 to n do b[i]:=true;
close(f);
assign(f,fo);rewrite(f);
End;
Procedure PrintResult;
var i:integer;
Begin
for i:=1 to n do write(f,' ',x[i]); writeln(f);
End;
procedure Try(i:integer);
Var j:integer;
Begin
for j:=1 to n do
if b[j] then
begin
x[i]:=j;
if i=n then PrintResult
else begin
b[j]:=false;
try(i+1);
b[j]:=true;
end
end;
End;
BEGIN
Init;
Try(1);
Close(f);
END.
d. Tổ hợp
Phát biểu: Một tổ hợp chập k của n phần tử là một bộ không kể thứ tự gồm k
thành phần khác nhau lấy từ n phần tử đã cho, hai thành phần bất kì trong bộ đó phải
khác nhau. (n>1, k>0, n>k). Nói cách khác, một tổ hợp chập k của n phần tử là một
tập con k phần tử của nó.
Ví dụ 7: chập 3 của 5 phần tử
Sử dụng mảng X(x1, x2…xk) chứa các phần tử của một tổ hợp chập k của N.
Vấn đề là ta phải xác định xem giá trị của xi (i=1..n)là gì và có phải dùng kỹ thuật
đánh dấu các giá trị đã được lựa chọn hay không.
– Quan sát từ hình vẽ: Xét một nút bất kỳ có giá trị A thì các nút tiếp theo thuộc
nhánh trái nhất sẽ là A+1, nút phải nhất tối đa là N. Xét thêm một vài nhánh để xem
có xác định mộ giá trị tốt hơn N không? Từ hình vẽ ta bổ sung thêm phần tử x 0=0 là
giá trị cho nút khởi đầu.
x0=0
i=1,xi=các đề cử mới(1, 2, 3), x0+1≤xi≤n-k+i, chọn x1=1
i=2,xi=các đề cử mới(2, 3, 4), x1+1≤xi≤n-k+i, chọn x2=2
i=3,xi=các đề cử mới(3, 4, 5), x2+1≤xi≤n-k+i,
chọn x3=3; i=k => một tổ hợp 1, 2, 3
chọn x3=4; i=k => một tổ hợp 1, 2, 4
read(f,n);
close(f);
assign(f,fo);rewrite(f);
x[0]:=0;
End;
Procedure PrintResult;
var i:integer;
Begin
d:=d+1;
write(f,d:4,': ');
for i:=1 to k do write(f,' ',x[i]);
writeln(f);
End;
procedure Try(i:integer);
Var j:integer;
Begin
for j:=x[i-1]+1 to n-k+i do {}
begin
x[i]:=j;
if i<k then try(i+1) else PrintResult;
end;
End;
BEGIN
Init;
for k:=1 to n do Try(1);
close(f);
END.
(Chú ý: Bạn đọc hãy tìm hiểu kỹ các bài toán trên trong phần cài đặt chương
trình tại các vị trí đã được đánh dấu nghiên, đậm và cũng có thể chưa đánh dấu. Vì
nếu lướt qua ta sẽ thấy các bài này na ná giống nhau, nhưng thực chất là khác nhau) .
Liệt kê các dãy có chiều dài n dưới dạng x1,x2...xn, trong đó xi={0,1}. Ta có thể
sử dụng sơ đồ tìm tất cả các lời giải của bài toán. Hàm Try(i) xác định xi, trong đó xi
chỉ có 1 trong 2 giá trị là 0 hay 1. Các giá trị này mặc nhiên, được chấp nhận mà
không cần phải thoả mãn điều kiện gì. Nên Hàm try(i) có thể viết như sau :
Procedure Try ( i);
Var I,j:integer;
Begin
for j = 0 to 1 do
begin
x[i]=j;
if (i < n ) then Try (i+1);
else PrintX;
end;
end;
Thủ tục PrintX: mỗi dãy nhị phân viết trên một dòng đặt trong tệp văn bản.
Procedure PrintX;
Var i:integer;
Begin
Tóm lại nếu c[i]=c1[i+j]=c2[i-j]=true thì có thể đặt hậu i vào cột j.
Dữ liệu: Hau.inp chứa kích thức bàn cờ số nguyên n
Kết quả: Hau.out các vị trí hậu trên từng bàn cờ.
{Chương trình xếp n hậu}
const MaxN=10;
fi='hau.inp';{chua so N}
fo='hau.out';
var
h:array[1..MaxN] of integer;
c:array[1..MaxN] of boolean;
c1:array[2..2*MaxN] of boolean;
c2:array[1-MaxN..MaxN+1] of boolean;
n:integer;
f:text;
procedure Init;
var i,j:integer;
Begin
assign(f,fi);reset(f);
read(f,n);
for i:=1 to n do c[i]:=true;
for i:=2 to 2*n do c1[i]:=true;
for i:=1-n to n+1 do c2[i]:=true;
close(f);
assign(f,fo);rewrite(f);
End;
Procedure PrintResult;
var i,j:integer;
Begin
writeln(f,'==========');
for i:=1 to n do begin
for j:=1 to n do if h[i]=j then write(f,'H') else write(f,'O');
writeln(f);
end;
writeln(f);
End;
procedure Try(i:integer);
Var j,k:integer;
Begin
for j:=1 to n do
if c[j] and c1[i+j] and c2[i-j] then
begin
h[i]:=j;
if i=n then PrintResult
else begin
c[j]:=false;c1[i+j]:=false;c2[i-j]:=false;
Try(i+1);
c[j]:=true;c1[i+j]:=true;c2[i-j]:=true;
end;
end;
End;
BEGIN
Init;
Try(1);
close(f);
END.
với n=4, kết quả tệp hau.out như sau:
==========
OHOO
OOOH
HOOO
OOHO
==========
OOHO
HOOO
OOOH
OHOO
writeln(f);
End;
Procedure Try(i:integer;x,y:integer);
Var j,k,u,v:integer;
Begin
for j:=1 to 8 do
begin
u:=x+a[j];
v:=y+b[j];
if (ma[u,v]=0)and(u>=1)and(u<=n)and(v>=1)and(v<=n) then
begin
ma[u,v]:=i;
if i=n*n then PrintResult
else Try(i+1,u,v);
ma[u,v]:=0;
end;
end;
End;
BEGIN
Init;
Try(2,x,y);
Close(f);
END.
h. Mê cung (later)
Quay lui là ban đầu ta tiến, tiến và tiến cho đến khi không tiến được nữa thì
lui và lui cho đến khi có đường đi mới, ta lại tiến và tiến… lại lui, lui cho đến khi hết
đường lui. Hết việc!
a. Thầy nguyễn Thanh Tùng tóm tắt về TÌM KIẾM QUAY LUI.
Tìm kiếm quay lui (Backtraking) là phương pháp tìm kiếm lời giải trên cây
phương án. Phương pháp này được sử dụng chủ yếu để tìm một lời giải thỏa mãn một
số yêu cầu nào đó theo nguyên tắc mở rộng dần dần lời giải.
Các bài toán điển hình:
Mã đi tuần: Tìm đường đi của quân mã trên trên bàn cờ kích thước NN, sao cho
mỗi ô trên bàn cờ (trừ ô xuất phát) được đi qua đúng một lần và trở về ô xuất phát,
Đặt quân hậu: Đặt ttối đa số quân hậu trên bàn cờ NN, sao cho không có hai
quân hậu nào ở vị trí ăn nhau,
Bài toán người bán hàng rong: Tìm đường đi khép kín ngắn nhất qua mỗi đỉnh
của đồ thị đúng một lần,
Xâu không lặp: Với M ký tự khác nhau hãy tìm xâu có độ dài N, sao cho mọi
xâu con L ký tự liên tiếp đều khác nhau,
Xâu phản đối xứng: Với M ký tự khác nhau hãy tìm xâu có độ dài N, sao cho
nếu lấy xâu con bất kỳ các ký tự liên tiếp nhau và chia nó thành hai xâu con liên tiếp
nhau, thì hai xâu con mới này khác nhau với mọi cách chia,
Thoát khỏi mê cung: Điều khiển rô bốt tìm đường ra khỏi mê cung.
Nguyên tắc cơ bản của phương pháp tìm kiếm quay lui là như sau:
Chia tập tìm kiếm thành nhiều tập con,
Đánh số các tập con theo một tiêu chuẩn nào đó có các tập P1, P2, . . ., Pk,
P
P1 P3
P2
P11 P14
P12
Với mỗi tập con nhận được: xác định một trong số các khả năng:
Không cần xét tiếp tập này,
Bài toán trở nên quá đơn giản và tìm được một lời giải,
until kt;
n
Giá của phương án x bằng: f(x) = c
i 1
i * xi
Kết luận:
Bài toán cái túi là một bài toán tối ưu tổ hợp: “Tìm Max{f(x)| xD}, với
D={x|x=(x1,x2,…,xn), xi = 0 hoặc 1, i=1,2,…,n; và g(x) < b}
Ví dụ 2: Bài toán thuê thợ
Có n công việc được đánh số từ 1 đến n và có n thợ được đánh số từ 1 đến n.
Mỗi công việc i biết chi phí cần trả để thợ j hoàn thành công việc này là c[i,j],
với i,j = 1,2,3,..,n.
Yêu cầu: Hãy tìm phương án thuê thợ với tổng chi phí phải trả là nhỏ nhất sao
cho các công việc đều hoàn thành, biết rằng mỗi thợ chỉ thực hiện một công việc và
mỗi việc chỉ do một thợ thực hiện.
Dữ liệu: Vào từ file văn bản THUETHO.INP có dạng
Dòng đầu ghi số n. (N <= 100)
n dòng tiếp theo, mỗi dòng i ghi n số c[i,j], j = 1,2…,n tương ứng là chi phí
cần trả để thợ j hoàn thành công việc i (i = 1,2,…,n).
Kết quả: Ghi ra file văn bản THUETHO.OUT gồm:
Dòng đầu ghi tổng chi phí thuê thợ.
Dòng thứ 2 ghi n số, trong đó số thứ i (i=1,2,..n) là số hiệu của thợ thực hiện
công việc i.
Ví dụ:
THUETHO.INP THUETHO.OUT
5 32
4 8 17 19 7 1 2 5 4 3
12 6 17 19 10
8 19 19 7 7
17 9 17 3 7
7 16 12 16 8
*Phân tích:
– Mỗi phương án thuê thợ là một hoán vị của n phần tử {1,2, ...,n}. Kí hiệu
x={x1,x2, ...,xn} trong đó xi = j nghĩa là việc i thuê thợ j; i=1,2,..,n; j {1,2, ...,n}.
– Tập các phương án: D = {x| x = {x1,x2, ...,xn}, xi {1,2, ...,n}, i=1,2,..,n và
xi<>xj nếu i<>j }
n
– Giá của phương án: f(x) = C[i, xi ]
i
Bài toán đưa về bài toán tối ưu tổ hợp: “Tìm Min{f(x)| x D}”
{}
Procedure Cập Nhật Kỷ Lục;
begin
- <Tính giá phương án nếu chưa tính>
- Nếu giá p/án > kỷ lục (hoặcp/án > kỷ lục) thì
begin
kỷ lục := giá p/án;
giữ lại p/án;
end;
end;
Procedure Try(k : integer);
begin
for ak Ak do
if <chấp nhận ak> then
begin
xk := ak;
<Tính giá của phương án bộ phận cấp k;>
<Xác định trạng thái mới của bài toán nếu cần>
if k = n then Cập Nhật Kỷ Lục
else try(k+1);
<Hoàn nguyên trạng thái của bài toán nếu cần>
end;
end;
Procedure Inkq;
begin
<in kỷ lục và phương án tối ưu>
end;
BEGIN
Khởitạo;
Try(1);
inkq;
END.
Bài toán đưa về duyệt các chỉnh hợp lặp chập n của 3 phần tử {0,1,2}, tính giá
của từng phương án và cập nhật kỷ lục để giữ lại phương án tốt nhất.
Để tăng hiệu quả trong việc duyệt trước hết ta sắp xếp các quả cân giảm dần theo
trọng lượng và thứ tự đề cử cho mỗi thành phần x[i] là 0, 1, 2.
Cách 1: Không dùng thêm tham số trong thủ tục Try
{ chay cham ,n < 50}
Program CanThangBang;
uses crt;
const
fi ='can.inp';
fo ='can.out';
maxn=300;
var a,a1 :array[1..maxn] of integer;
kqx1,kqx2,x1,x2 :array[1..maxn] of integer;
tongtua :array[1..maxn] of longint;
{tongtua[i] := a[i+1] + a[i+2] + … + a[n]}
n, p :integer;
le, ri :longint;
{tổng khối lượng các quả cân được đặt vào đĩa chứa vật và đĩa không chứa vật
của phương án bộ phận, ban đầu le := p; ri :=0}
min,t,d,d1,d2,kqd1,kqd2 :integer;
f:text;
cs :array[1..maxn] of integer;
Procedure init;
var i:integer;
Begin
assign(f,fi); reset(f);
readln(f,n);
for i:=1 to n do read(f,a[i]);
a1:=a;
readln(f,p);
close(f);
le := p;
ri := 0;
min := maxint;
d1 := 0;
d2 := 0;
tongtua[n]:=a[n];
for i:=n-1 downto 1 do tongtua[i]:=tongtua[i+1]+a[i];
end;
{------------------------------*****---------------------------}
procedure Sort;
var i,j,tg: integer;
begin
for i:=1 to n do cs[i]:=i;
for i:=1 to n-1 do
for j:=i+1 to n do
if a[j]>a[i] then
begin
tg:=a[i];a[i]:=a[j];a[j]:=tg;
tg:=cs[i];cs[i]:=cs[j];cs[j]:=tg;
end;
tongtua[n]:=a[n];
for i:=n-1 downto 1 do tongtua[i]:=tongtua[i+1]+a[i];
end;
Procedure cnkl;
Begin
if d < min then
Begin
min := d;
kqd1 := d1;
kqd2 := d2;
kqx1 := x1;
kqx2 := x2;
End;
End;
Procedure try(i:integer);
var j:integer;
Begin
for j:=0 to 2 do
Begin
if j=1 then
Begin
inc(d1);
x1[d1] := i;
le :=le + a[i];
End;
if j=2 then
Begin
inc(d2);
x2[d2] := i;
ri := ri + a[i];
End;
d:=d1+d2;
if i=n then Begin if le=ri then cnkl; End
else if (d < min) and (abs(le - ri) <= tongtua[i + 1]) then try(i + 1);
if j=1 then
Begin
dec(d1);
le := le - a[i];
End;
if j=2 then
Begin
dec(d2);
ri := ri - a[i];
End;
d := d1 + d2;
end;
End;
Procedure result;
var i:integer;
Begin
assign(f,fo); rewrite(f);
write(f,kqd1,' ');
for i:=1 to kqd1 do write(f,cs[kqx1[i]],' ');
writeln(f);
write(f,kqd2,' ');
for i:=1 to kqd2 do write(f,cs[kqx2[i]],' ');
close(f);
End;
BEGIN
init;
sort;
try(1);
result;
END.
Cách 2: Dùng thêm tham số trong thủ tục Try
Mỗi quả cân có 3 khả năng lựa chọn (hay còn gọi là có 3 trạng thái) được kí hiệu
là 1,0,-1, đó là: đặt ở đĩa có vật (1), không đặt vào đĩa nào (0), đặt ở đĩa không có vật
(-1);
Mỗi phương án (mỗi cấu hình) của bài toán là bộ: x[1], x[2], …, x[n] trong
đó x[i] là khả năng lựa chọn của quả cân i: x[i] = -1,0,1 (i=1..n).
Trong thủ tục Try: S là khối lượng chênh lệch giữa hai đĩa cân ở bước i-1 truyền
lại cho bước thứ i, sau đó được tính lại khi đã chọn xong ở bước i và truyền lại cho
bước i+1
program CanThangBang;
uses crt;
const fin='Can.inp';
fout='Can.out';
maxn=70;
var a,cs : array[1..maxn] of integer;
x,kqx : array[1..maxn] of integer;
ts : array[1..maxn] of longint;{tổng sau ts[i]:=a[i+1]+a[i+2]+…+a[n]}
n,d,t : integer;
min,kqi : integer;
f : text;
p,s:longint;
{s là chênh lệch khối lượng giữ hai đĩa cân, được khởi tạo ban đầu là p}
{d là số quả cân được chọn đặt lên đĩa}
procedure Init;
var i: integer;
begin
assign(f,fin);reset(f);
readln(f,n);
for i:=1 to n do read(f,a[i]);
readln(f);
readln(f,p);
close(f);
min:=maxint;
s:=p;
d:=0;
t:=0;
end;
procedure Sort;
var i,j,tg: integer;
begin
for i:=1 to n do cs[i]:=i;
for i:=1 to n-1 do
for j:=i+1 to n do
if a[j]>a[i] then
begin
tg:=a[i];a[i]:=a[j];a[j]:=tg;
tg:=cs[i];cs[i]:=cs[j];cs[j]:=tg;
end;
ts[n]:=0;
for i:=n-1 downto 1 do ts[i]:=a[i+1]+ts[i+1];
end;
Procedure Try(i,s,d: integer);
{d là số quả cân đã dùng đến bước i-1 truyền lại cho bước i}
var j: integer;
begin
for j:=1 downto -1 do
if d+abs(j)<min then {có thể đánh giá ngay tại đây: nếu thêm 1 quả cân nữa
đặt vào đĩa mà không tốt hơn kỷ lục của phương án mẫu thì không xây dựng tiếp pa}
begin
t:=s-j*a[i];
if abs(t)<=ts[i] then {độ lệch phải < tổng các quả cân chưa chọn}
begin
x[i]:=j;
if t=0 then
if d+abs(j)<min then
begin
min:=d+abs(j);
kqi:=i;
kqx:=x;
exit;
end;
if i<n then Try(i+1,t,d+abs(j));
end;
end;
end;
procedure Result;
var c1,c2,i: integer;
begin
assign(f,fout);rewrite(f);
if min=maxint then writeln(f,-1)
else
begin
c1:=0;c2:=0;
for i:=1 to kqi do
begin
Ví dụ:
LIC* HDH.INP LIC* HDH.OUT
6 4
4 1 2 3 1 1 3 4 6 2 5
5 6 6 7 8
*Phân tích thuật toán:
- Một trình tự thực hiện các công việc (một phương án của bài toán) là một hoán
vị của n phần tử {1, 2, …, n}.
Cụ thể: mỗi phương án là 1 bộ x = (x1, x2, …,xn); trong đó xi {1, 2, …, n}, xi
<> xj nếu i<>j;
- Giá của phương án x (số công việc đúng hạn) bằng: f(x) = k
i
Với điều kiện g(x) = p < di với mọi i = 1,2, …, k
i
i =1
Bài toán lập lịch đúng hạn đưa về bài toán: “Tìm Max{f(x)| xD},
với D={x|x=(x1,x2,…,xn), xi {1, 2, …, n}, xi <> xj nếu i<>j; và g(x) < di}
Văn bản chương trình:
program LapLichUuTienDungHan;
uses crt;
const fin = 'LIC* HD.INP';
fout = 'LIC* HD.OUT';
maxn = 100;
var p,d,x,kqx : array[1..maxn] of integer;
cx,kqcx : array[1..maxn] of boolean;
f : text;
n : integer;
tg,count,max: integer;
{----------------------------------------------------------}
procedure Init;
var i: integer;
begin
assign(f,fin);reset(f); readln(f,n);
for i:=1 to n do read(f,p[i]);
readln(f);
for i:=1 to n do read(f,d[i]);
close(f);
fillchar(cx,sizeof(cx),true);
tg := 0; count:=0; max:=0;
end;
{----------------------------------------------------------}
procedure Try(i: integer);
var j: integer;
begin
for j:=1 to n do
begin
if cx[j] then
begin
x[i]:=j;
cx[j]:=false;
tg := tg + p[j];
if (tg <= d[j]) then inc(count);
if (i = n) then
begin
if count>max then
begin
max:=count;
kqx:=x;
end;
end
else if count+n-i>max then Try(i+1);
if tg <= d[j] then dec(count);
tg := tg - p[j];
cx[j]:=true;
end;
end;
end;
{---------------------------------------------------------}
procedure Result;
var i: integer;
j: integer;
begin
assign(f,fout);rewrite(f);
writeln(f,max);
for i:=1 to n do write(f,kqx[i],' ');
close(f);
end;
{----------------------------------------------------------}
BEGIN
Init;
Try(1);
Result;
END.
Bài 4 : Truyền tin
Người ta cần truyền n gói tin được đánh số từ 1 đến n từ một điểm phát đến một
điểm thu. Để thực hiện việc truyền tin có thể sử dụng m đường truyền được đánh số
từ 1 đến m. Biết rằng nếu truyền j gói tin theo đường truyền tin i thì chi phí phải trả là
sij (sij là số nguyên dương, sij < 32767, i =1,2,..,m, j = 1,2,…,n).
Yêu cầu: Hãy xác định số lượng gói tin cần truyền theo mỗi đường truyền tin để
việc truyền n gói tin được thực hiện với tổng chi phí phải trả là nhỏ nhất.
Dữ liệu: Vào từ file văn bản TTIN1.INP:
+ Dòng đầu tiên chứa hai số nguyên dương n và m (n,m < 100) .
+ Dòng thứ i trong số m dòng tiếp theo chứa n số nguyên dương s i1, si2, .., sin ,
i=1,2,…,m.
Kết quả: Đưa ra file văn bản TTIN1.OUT:
+ Dòng đầu tiên chứa S là tổng chi phí phải trả theo cách truyền tin tìm được.
+ Dòng thứ hai chứa m số nguyên không âm q1, q2, .. ,qm, trong đó qi là số gói tin
cần truyền theo đường truyền tin i.
Ví dụ:
TTIN1.INP TTIN1.OUT
3 3 4
20 20 20 0 2 1
4 3 10
1 3 20
ngày đó. Hãy lập một phương án cho thuê máy để tổng tần số sử dụng máy trong
tháng là lớn nhất.
Dữ liệu: Vào từ file văn bản THUEMAY.INP gồm:
+ Dòng đầu ghi các giá trị n, m.
+ m dòng tiếp theo, theo thứ tự 1, 2,…,m, mỗi dòng ghi yêu cầu của một khách:
bắt đầu là số ngày mà khách cần thuê, tiếp theo là các ngày trong tháng mà khách cần
thuê. Các giá trị trên cùng một dòng ghi cách nhau ít nhất một dấu trắng.
Kết quả: Đưa ra file văn bản THUEMAY.OUT:
+ Dòng đầu ghi số khách được thuê.
+ Dòng tiếp theo ghi các số hiệu khách được thuê, các số hiệu này phân cách
nhau ít nhất một dấu trắng.
+ Dòng cuối ghi tổng tần số sử dụng máy.
Hạn chế kích thước: số máy, số khách không vướt quá 30
Ví dụ:
THUEMAY.INP THUEMAY.OUT
3 10 6
5 1 3 4 6 7 1 2 6 8 9 10
1 1 20
2 3 6
3 2 4 5
6 1 2 3 4 5 6
2 2 3
5 1 3 4 6 7
5 2 4 5 6 7
4 1 2 4 5
3 3 5 6
Bài 6 : Dãy số
Cho dãy số gồm n số nguyên dương a1, a2, …, an (n < 1000) và số nguyên dương
k (k < 50).
Yêu cầu: Tìm dãy con nhiều phần tử nhất của dãy đã cho và có tổng các phần tử
chia hết cho k.
Dữ liệu: Vào từ file văn bản DAYSO.INP gồm:
+ Dòng đầu tiên chứa hai số n, k được ghi cách nhau bởi ít nhất một dấu trắng.
+ Các dòng tiếp theo chứa các số a1, a2, … ,an được ghi cách nhau ít nhất một
dấu trắng hoặc dấu xuống dòng.
Kết quả: Ghi ra file văn bản DAYSO.OUT
+ Dòng đầu tiên ghi m là số phần tử của dãy con tìm được.
+ Các dòng tiếp theo ghi dãy m chỉ số các phần tử của dãy đã cho có mặt trong
dãy con tìm được. Các chỉ số được ghi cách nhau ít nhất một dấu trắng hoặc xuống
dòng.
Ví dụ:
DAYCON.INP DAYCON.OUT
10 3 9
2 3 5 7 1 3 2 4 5
9 6 12 7 6 7 10 8
11 15
* Phân tích thuật toán: Chia từng số hạng của dãy cho k để tìm số dư, tính tổng
các số dư. Sau đó tính r := tổngdư mod k;
+ Nếu r = 0 thì kết luận lấy được cả dãy
+ Ngược lại: nếu tìm trong dãy có 1 số chia cho k dư r thì chỉ cần loại số đó và
dãy cần tìm gồm n-1 số còn lại;
Nếu không rơi vào 2 trường hợp trên thì thì ta đi “tìm cách loại đi ít nhất các
phần tử để tổng các số còn lại chia hết cho k”:
Trước hết ta đi tìm số lượng từng loại số dư: SLDU[i] = j nghĩa là các số
chia cho k dư i có j số (i=0,1,2, …,k-1)
Mỗi cách loại các phần tử được xác định bởi 1 bộ: x[1], x[2], …, x[n], trong
đó x[i]=j, nghĩa là các số chia cho k dư i cần loại đi j số sao cho tổng các số còn
lại chia hết cho k.
Rõ ràng khi đi xây dựng các x[i] thì giá trị đề cử ở bước i sẽ là các số
nguyên từ 0..SLDU[i].
Bài toán đưa về: Duyệt các cách loại phần tử (các bộ x[1], x[2], …, x[n], x[i] =
0..SLDU[i]), sau đó cập nhật kỷ lục để tìm ra 1 cách loại ít phần tử nhất. Kết quả của
bài toán sẽ gồm các phần tử không bị loại, khi đó sẽ gồm nhiều phần tử nhất.
Như vậy, từ 1 bài toán tìm Max ta đã chuyển về 1 bài toán tìm Min có độ
phức tạp nhỏ hơn (thời gian thực hiện nhanh hơn), đưa về việc giải 1 bài toán có kích
thước nhỏ hơn, đơn giản hơn;
{Chương trình này có thể chạy nhanh với n=10000, k= 1000}
program Dayso;
uses crt;
const
fi = 'DAYCON.INP';
fo = 'DAYCON.OUT';
maxn = 10000;
maxk = 1000;
type
ksldu = array [0..maxk-1] of integer;
kad = array [1..maxn ] of integer;
kloai = array[1..maxn] of boolean;
kx = array [1..maxk-1] of integer;
var
a : kad;
du : kad;{du[i] = j nghia la phan tu thu i chia cho k duoc so du la j}
sldu : ksldu;{sldu[i] = j so luong cac so chia cho k du i la bang j}
loai : kloai;
{danh dau cac chi so can loai bo}
x,kqx : kx;
n, k, d : integer;
min, tongdu, r,tdloai, slloai : longint;
f : Text;
procedure nhap;
var
inp: text;
i, t: integer;
begin
assign(f, fi); reset(f);
readln(f, n, k);
for i:=1 to n do read(f,a[i]);
close(f);
tongdu := 0;
for i:=1 to n do begin du[i] := a[i] mod k; tongdu:= tongdu+du[i]; end;
r := tongdu mod k;
assign(f,fo); rewrite(f);
end;
procedure XetDacBiet;
var
i, j: integer;
begin
if r=0 then
begin
writeln(f,n);
for i:=1 to n do
begin write(f,i:6); if (i mod 100) = 0 then writeln(f); end;
close(f);
halt;
end;
for i:=1 to n do loai[i] := false;
j :=0;
for i:=1 to n do if du[i] = r then
begin
loai[i] := true; j := i; break;
end;
if j>0 then
begin
writeln(f,n-1);
for i:=1 to n do
if not loai[i] then
begin write(f,i,' '); if (i mod 100) = 0then writeln(f); end;
close(f);
halt;
end;
end;
procedure try(i: integer);
var j : integer;
begin
for j:=0 to sldu[i] do
begin
x[i] := j; {nghĩa là: trong các số chia cho k dư i cần loại đi j số}
slloai := slloai + j; {số lượng số cần loại đi}
tdloai := tdloai + i*j; {tổng dư của các số loại đi}
if i = k-1 then
begin
if (tdloai mod k = r) and (slloai < min) then
begin
min := slloai;
kqx := x;
end;
end
else if slloai < min then try(i+1);
tdloai := tdloai - i*j;
slloai := slloai - j;
end;
end;
Procedure Tim;
var i: integer;
Begin
for i:=1 to k-1 do sldu[i] :=0;
for i:=1 to n do sldu[du[i]] := sldu[du[i]] + 1;
slloai := 0; tdloai := 0;
min := maxlongint;
try(1);
for i:=1 to n do loai[i] := false;
for i:=1 to n do
if kqx[du[i]] <>0 then
begin
dec(kqx[du[i]]);
loai[i] := true;
end;
writeln(f,n-min);
for i:=1 to n do
if not loai[i] then
begin
write(f,i,' ');
if i mod 100 =0 then writeln(f);
end;
close(f);
End;
begin
nhap;
XetDacBiet;
Tim;
end.
Bài 7: Chia kẹo
Có N gói kẹo (N nguyên dương, N < 50), các gói kẹo được đánh số từ 1 đến N,
gói kẹo thứ i có Ai cái kẹo (Ai nguyên dương và Ai < 20; i = 1, 2, 3, …, N).
Hãy tìm một cách chia các gói kẹo thành hai nhóm sao cho tổng số kẹo trong hai
nhóm chênh lệch nhau ít nhất.
Dữ liệu: Nhập N và N số A1, A2, …, AN từ file văn bản KEO.INP có cấu trúc
gồm 2 dòng: dòng đầu ghi số N, dòng thứ hai ghi các số lần lượt từ A1 đến AN. Các
số bắt đầu ghi từ đầu dòng theo thứ tự từ trái sang phải.
Kết quả: Ghi ra tệp văn bản KEO.OUT gồm 3 dòng:
Dòng đầu tiên ghi 3 số X, Y, Z, trong đó X là số kẹo chênh lệch giữa hai nhóm,
Y là số kẹo của nhóm 1, Z là số kẹo của nhóm 2;
Dòng thứ hai ghi số hiệu các gói kẹo của nhóm 1.
Dòng thứ ba ghi số hiệu các gói kẹo của nhóm 2.
Ví dụ:
KEO.INP KEO.OUT
8 1 56 57
19 5 20 13 16 20 18 2 3 5 6
12478
Bài 8: Xếp hàng mua vé
Có N người xếp hàng mua vé. Ta đánh số họ từ 1 đến N theo thứ tự đứng trong
hàng (từ đầu hàng đến cuối hàng). Thời gian phục vụ bán vé cho người thứ i là t i. Mỗi
người cần mua một vé nhưng được quyền mua tối đa 2 vé, vì thế một số người có thể
nhờ người đứng ngay trước mình mua hộ. Người thứ i nhận mua hộ vé cho người thứ
i+1 thì thời gian mua vé cho 2 người là ri.
Yêu cầu: Tìm phương án sao cho N người đều có vé với thời gian ít nhất.
Dữ liệu: đọc từ file văn bản MUAVE.INP gồm có 3 dòng, dòng thứ nhất ghi số
nguyên dương N, dòng thứ hai ghi N số t1, t2,…, tN, dòng thứ ba ghi N-1số r1, r2, …,
rN-1.
Kết quả: ghi ra file văn bản MUAVE.OUT gồm: dòng thứ nhất là tổng thời gian
phục vụ bán vé. Dòng tiếp theo ghi chỉ số của các khách hàng cần rời khỏi hàng
Ví dụ:
MUAVE.INP MUAVE.O
UT
5 17
2 5 7 8 4 2 4
3 9 10 10
Bài 9: Tạo chuỗi kí tự
Cho 3 ký tự A, B, C và một số nguyên dương N ( 4 <=N<=100).
Yêu cầu: Từ 3 ký tự trên hãy viết chương trình tạo ra chuỗi ký tự thoả mãn các
tính chất sau:
Có độ dài N.
Không có hai chuỗi con liên tiếp nào giống nhau.
Số ký tự B là ít nhất
Dữ liệu: Nhập số N từ file văn bản CHUOIKT.INP
Kết quả: Ghi ra file văn bản CHUOIKT.OUT có cấu trúc:
3
Bài 11: Trồng cây
Nhà Ông GĐ Sở văn hoá tỉnh BG có một đại sảnh rộng và đẹp. Ông có ý định
trồng một số loại cây cảnh vào N chậu hoa đã được thiết kế cố định thành một hàng
trên đại sảnh, các chậu hoa có số thứ tự từ 1 đến N tính từ trái sang phải. Biết được ý
thích của Ông, các bạn bè đã mua tặng ông M cây cảnh khác nhau để trồng vào các
chậu hoa nói trên. Mỗi cây cảnh có một số hiệu duy nhất trong khoảng từ 1 đến M.
Ông muốn trồng các cây cảnh vào các chậu hoa theo thứ tự ràng buộc “nếu có cây
cảnh số hiệu i và cây cảnh số hiệu j mà i<j thì cây cảnh số hiệu i sẽ được trồng bên
trái cây cảnh số hiệu j”. Tất cả các cây cảnh đều được trồng vào các chậu hoa sao cho
đảm bảo ràng buộc về thứ tự các số hiệu. Nếu số chậu hoa nhiều hơn số cây cảnh thì
những chậu hoa không dùng tới sẽ để trống. Mỗi chậu hoa chỉ được trồng một cây
cảnh.
Mỗi chậu hoa có thiết kế về hình dáng, kích thước, kiểu cách có thể khác nhau
(các cây cảnh cũng có thể khác nhau). Vì thế, khi một cây cảnh i được trồng vào một
chậu hoa j sẽ có một giá trị thẩm mỹ nhất định, giá trị này được biểu diễn bởi một số
nguyên aij. Các chậu hoa để trống có giá trị bằng 0.
Yêu cầu: Hãy tìm cách trồng các cây cảnh vào các chậu hoa tuân theo ràng buộc
về thứ tự sao cho tổng các giá trị thẩm mỹ là lớn nhất. Nếu có nhiều cách trồng có
cùng giá trị thẩm mỹ thì chỉ cần đưa ra đúng một cách.
Dữ liệu: vào từ file văn bản TREE.INP gồm:
Dòng đầu tiên ghi 2 số M và N (1 < N < 100; M < N < 100).
M dòng tiếp theo, mỗi dòng ghi N số, dòng i ghi a[i,1],.., a[i,N] với -50<a[i,j]<
50 là già trị thẩm mỹ khi trồng cây cảnh i vào chậu hoa j.
Kết quả: ghi ra file văn bản TREE.OUT:
Dòng đầu tiên là tổng số thẩm mỹ của cách trồng.
Dòng thứ hai ghi M số, số thứ k ghi số hiệu của chậu hoa mà cây cảnh số hiệu
k trồng vào.
Ví dụ:
TREE.INP TREE.
OUT
35 53
7 23 -5 -24 2 4
16 5
5 21 -4 10
23
-21 5 -4 -
20 20
Bài 12 :Rôbốt quét vôi
Có 9 căn phòng (đánh số từ 1 đến 9) đã được quét vôi với mầu trắng, xanh hoặc
vàng. Có 9 rôbốt (đánh số từ 1 đến 9) phụ trách việc quét vôi các phòng. Mỗi rôbốt
chỉ quét vôi một số phòng nhất định. Việc quét vôi được thực hiện nhờ một chương
trình cài sẵn theo qui tắc:
nếu phòng đang có màu trắng thì quét màu xanh,
nếu phòng đang có màu xanh thì quét màu vàng,
nếu phòng đang có màu vàng thì quét màu trắng,
Cần phải gọi lần lượt một số các rôbốt ra quét vôi (mỗi lần một rôbốt, một rôbốt
có thể gọi nhiều lần và có thể có rôbốt không được gọi. Rôbốt được gọi sẽ quét vôi tất
cả các phòng mà nó phụ trách) để cuối cùng các phòng đều có màu trắng.
Hãy tìn một phương án như vậy sao cho lượng vôi phải quét là ít nhất. Giả thiết
rằng lượng vôi cho mỗi lượt quét đối với các phòng là như nhau.
Dữ liệu: vào từ fie văn bản ROBOTQV.INP gồm các dòng: 9 dòng đầu, mỗi
dòng mô tả danh sách các phòng được quét vôi bởi một rôbốt theo thứ tự từ rôbốt 1
đến rôbốt 9. Mỗi dòng như vậy gồm các số hiệu phòng viết sát nhau. Chẳng hạn dòng
thứ 2 có nội dung : 3578
Mô tả rôbốt 2 phụ trách việc quét vôi các phòng 3, 5, 7, 8.
Dòng cuối mô tả màu vôi ban đầu của các phòng. Dòng gồm 9 kí tự viết sát
nhau, kí tự thứ i biểu diễn màu vôi của phòng i với qui ước: kí tự T chỉ màu trắng, kí
tự X chỉ màu xanh, kí tự V chỉ màu vàng, .
Kết quả: ghi ra tệp văn bản ROBOTQV.OUT gồm một dòng dưới dạng:
Nếu không có phương án thì ghi một chữ số 0.
Trái lại ghi dãy thứ tự các rôbốt được gọi (các số hiệu rôbốt viết sát nhau)
Ví dụ:
ROBOTQV.INP ROBOTQV.INP
159 2455688
123
357
147
5
369
456
789
258
XVXVXVTXT
*Phân tích thuật toán
Mỗi phương án gọi Rôbốt là một chỉnh hợp lặp chập 9 của 3 phần tử {0,1,2}.
Kí hiệu x = (x1, x2, ...,xn); với xi := j nghĩa là Rôbốt i được gọi j lần.
Gọi r[i] là xâu kí tự cho biết danh sách các phòng do Rôbốt i phụ trách
9
Khi đó giá của phương án x = xi * length(r[i])
i
Kết quả: ghi ra file văn bản RENTING.OUT. Dòng đầu tiên ghi hai số nguyên
dương theo thứ tự là số lượng khách hàng nhận được phục vụ và tổng tiền thu được.
Dòng tiếp theo ghi chỉ số của khách hàng được phục vụ.
Ví dụ:
RENTING.INP RENTING.OUT RENTING.INP RENTING.OUT
3 2 180 4 2 1100
150 500 150 2 3 400 821 800 2 4
1 200 100 200 513 500
400 800 80 100 325 200
600 900 600
Bài 16: Xoay ô
Một diện tích được tạo bởi các ô vuông, gồm N hàng và N cột (các hàng được
đánh số từ 1 đến N theo chiều từ trên xuống dưới và các cột được đánh số từ 1 đến N
theo chiều từ trái sang phải). Mỗi ô vuông được chia đôi theo đường chéo: Một nửa
màu đen, một nửa màu trắng. Trạng thái của mỗi ô vuông được mã hóa từ 0 đến 3
theo hình vẽ sau:
0 1 2 3
Một diện tích được xác định bởi trạng thái các ô vuông của nó. Có thể thay đổi
trạng thái này bằng cách xoay các ô theo chiều kim đồng hồ một trong những góc 90,
180o, 270o.
Diện tích được gọi là hợp lệ nếu các phần cùng màu của các ô sát nhau không
được có chung cạnh, chẳng hạn diện tích trong hình 1 là không hợp lệ, hình 2 là hợp
lệ:
Hình 2 Hình 1
Hãy xác định xem diện tích đã cho đã hợp lệ chưa? Nếu chưa, cần tìm cách
xoay lại một số ô cần thiết để diện tích là hợp lệ, sao cho số ô cần xoay là ít nhất.
Dữ liệu vào cho trong file văn bản ROT.INF, dòng đầu là số N, tiếo theo là bảng
N dòng, N cột ghi trạng thái của các ô tương ứng. Các giá trị trên cùng một dòng ghi
cách nhau ít nhất một dấu trắng.
Lời giải đưa ra file văn bản ROT.INF, trong đó nếu diện tích đã cho là hợp lệ thì
chỉ cần ghi một số 0, trái lại văn bản gồm dòng đầu tiên là số ô cần xoay, các dòng
tiếp, mỗi dòng mỗi dòng mô tả một ô cần xoay bao gồm: tọa độ hàng, tọa độ cột và
góc cần xoay theo chiều kim dồng hồ của ô (tính theo đơn vị độ - là một trong các giá
trị 90, 180, 270). Các số này ghi cách nhau ít nhất một dấu trắng.
Thí dụ:
ROT.INF (Hình 1) ROT.OUT diện tích nhận được cho bởi hình 2.
4 6 Giới hạn kích thước N 10.
2 0 1 0 1 1 270
2 0 3 2 2 1 270
2 0 2 3 2 3 180
1 2 1 0 2 4 180
3 2 270
4 2 180
begin
d:=0;
for i:=1 to n do
for k:=i-1 downto 1 do
begin
b[k,i]:=otren[b[k,i-1],b[k+1,i]];
b[i,k]:=oduoi[b[i-1,k],b[i,k+1]];
end;
for i:=1 to n do
for j:=1 to n do
if a[i,j]<>b[i,j] then
Begin
inc(d);
if d > min then exit;
End;
if d < min then
begin
min:=d;
kqb:=b;
end;
end;
Procedure try(i:integer);
var j:integer;
begin
for j:=0 to 3 do
begin
b[i,i]:=j;
if i=n then cnkl else
try(i+1);
end;
end;
procedure viet;
var i,j,doo:integer;
begin
assign(f,fo);rewrite(f);
writeln(f,min);
for i:=1 to n do
for j:=1 to n do
if a[i,j]<>kqb[i,j] then
begin
doo:=(kqb[i,j]-a[i,j])*90;
Khi xếp các phiếu thành một chồng, các hình tròn ở cùng một vị trí tạo thành
một cột.
Yêu cầu: Hãy chỉ ra một số ít nhất các phiếu cần xoay sao cho nếu xếp chúng thành
một chồng thì trên mỗi cột đều có ít nhất một hình tròn được đục thục, hoặc cho biết
điều này không thể thực hiện được.
Dữ liệu: Vào từ file văn bản BN4.INF:
- Dòng đầu là 2 số nguyên N, K (1N20,
2K10).
- Dòng thứ i trong N dòng tiếp theo chứa
K*K số 0, 1 cho biết trạng thái các hình tròn của
phiếu thứ i, liệt kê theo hàng ngang, từ trái sang
phải và từ trên xuống dưới, trong đó số 1 (0) chỉ
ra vị trí tương ứng là lỗ đục (không đục).
Các số trên 1 dòng cách nhau ít nhất một
dấu cách.
Kết quả: Đưa ra file văn bản BL4.OUT:
- Dòng đầu tiên chứa số nguyên M là số phiếu cần xoay. M = -1 là không có cách
xoay.
- Trong trường hợp M 0, thì dòng thứ 2 chứa N số nguyên R1, R2,…, RN, Ri bằng 0,
1, 2 hoặc 3, cho biết bìa thứ i cần xoay Ri*90o theo chiều kim đồng hồ.
- Nếu có nhiều cách chọn M phiếu để xoay thì chỉ cần nêu một trong số đó.
Ví dụ:
BL4.INP BL4.OUT
5 3 3
1 0 0 0 1 0 0 0 0 0 0 1 1 3
0 1 0 1 0 0 0 0 0
1 0 0 0 1 0 0 0 1
0 1 0 0 0 0 1 0 0
1 0 0 1 0 0 0 0 0
Chú ý: - Các file bài làm phải được đặt tên tương ứng là BL3.PAS và BL4.PAS.
19 8 5 3 25 5 4
1 2 8 25
*Phân tích thuật toán:
Mỗi phương án của bài toán là một bộ có thứ tự gồm k thành phần: x = (x1, x2,
…, xk), trong đó xi = (dòng i, cột i), dòng i = 1, 2, …, m và cột i = 1, 2, …, n; nếu i
<> j thì xi <> xj ;
Vì dòng i có m khả năng lựa chọn và cột i có n khả năng lựa chọn nên bộ hai
thành phần (xi, yi) có m*n khả năng lựa chọn.
k
Giá của mỗi phương án bằng f(x) = a[xi]
1
“Bài toán đưa về: duyệt các chỉnh hợp không lặp chập k của m*n phần tử, tính
giá của các phương án, só sánh giá của các phương án với nhau và giữ lại phương án
có giá lớn nhất”
* Văn bản chương trình
Program chonKSoCotongLonNhat;
uses crt;
const
fi = 'CHONK.INP';
fo = 'CHONK.OUT';
Maxmn = 100;
var
a: array[1..maxmn,1..maxmn] of byte;
dong, cot, kqd, kqc : array[1..maxmn] of byte;
cxd, cxc : array[1..maxmn] of boolean;
m, n, k : byte;
tmax, s : longint;
procedure init;
var
f: text;
i,j : integer;
begin
assign(f,fi); reset(f);
readln(f,m,n,k);
for i := 1 to m do
for j:=1 to n do read(f,a[i,j]);
close(f);
fillchar(cxd, sizeof(cxd), true);
end;
end;
BEGIN
init;
try(1);
inkq;
END.
Bài 20: Tìm tổng nhỏ nhất
Cho ma trận gồm m dòng, n cột có các phần tử là các số nguyên a[i,j], trong đó
|a[i,j]|<30000 (i=1..m, j=1..n).
Yêu cầu: Hãy chọn trên mỗi dòng, mỗi cột của ma trận đúng một số sao cho
không có hai số nào thuộc cùng một dòng hay cùng một cột để tổng các số được chọn
là nhỏ nhất.
Dữ liệu: Vào từ file văn bản TONGMIN.INP có cấu trúc:
Dòng đầu ghi 2 số nguyên m, n. (1 < m < 100, 1 < n < 30);
Trong m dòng tiếp theo, dòng thứ i ghi n số a[i,j] với i=1..m, j=1..n;
Kết quả: Ghi ra file văn bản TONGMIN.OUT có cấu trúc:
Dòng đầu tiên ghi tổng nhỏ nhất tìm được
Trong n dòng tiếp theo, mỗi dòng ghi 3 số: số thứ nhất là giá trị của số được
chọn, hai số sau là toạ độ [dòng, cột] của số được chọn.
Ví dụ:
TONGMIN.INP TONGMIN.OUT
7 4 -67
8 6 -18 0 -18 1 3
-15 1 15 0 -15 2 1
-5 0 9 13 -14 4 2
11 -14 -12 12 -20 7 4
19 3 14 12
20 -4 2 17
10 -8 5 -20
*Phân tích thuật toán:
* Nếu m < n thì : “duyệt các chỉnh hợp không lặp chập m của n phần tử
{1,2,…,n}, tức là mỗi phương án cần tìm là một bộ x = (x1, x2, …, xm), xi = j nghĩa
là chọn số ở hàng i cột j; i = 1, 2,.., m; j = 1,2, …,n; tính giá của phương án x bằng
m
công thức f(x) = a[i,x[i]] và giữ lại phương án có giá trị nhỏ nhất”
i=1
* Nếu m > n thì : “duyệt các chỉnh hợp không lặp chập n của m phần tử
{1,2,…,m}, tức là mỗi phương án cần tìm là một bộ x = (x1, x2, …, xn), xi = j nghĩa
là chọn số ở cột i hàng j; i = 1, 2,.., n; j = 1,2, …,m; tính giá của phương án x bằng
n
công thức f(x) = a[x[i],i] và giữ lại phương án có giá trị nhỏ nhất”
i=1
KẾT LUẬN
Hiện nay chưa có tài liệu chính thức dành cho lớp Chuyên Tin khối THPT. Mà
chỉ tồn tại ở dưới dạng tên chuyên đề của Bộ Giáo dục gửi về cho các trường. Do vậy
việc dạy và học gặp nhiều khó khăn trong việc thống nhất về nội dung cũng như mức
độ về kiến thức chuyên môn. Nên việc nghiên cứu và xây dựng chuyên đề này là thực
sự cần thiết.
Mục đích của chuyên đề là truyền tải một cách tổng quát, chi tiết và sự thống
nhất về mặt nội dung để người đọc dễ hiểu và thấy được tầm quan trọng của bài toán
liệt kê tổ hợp và một số bài tập ứng dụng trong Tin học dành cho học sinh chuyên tin
ở trường THPT.
Nội dung của chuyên đề đã được các tác giả chọn lọc qua chương trình đã thực
dạy tại trường và tham khảo của một số đồng nghiệp khác trong nước.
Kiến thức là vô hạn, trong chuyên đề này chúng tôi đã có gắng sáng tạo, sưu
tầm và trao đổi một cách tốt nhất có thể về mặt lý thuyết, các bài tập ứng dụng và các
bài tập trong các kỳ thi trong nước để tạo thành cuốn tài liệu hiệu quả dành cho giáo
viên và các em học sinh đam mê tin học chính thức làm tài liệu cho mình trong quá
trình học tập và nghiên cứu.
MỤC LỤC
MỞ ĐẦU .............................................................................................................................................. 1
PHẦN 1. LÝ THUYẾT TỔ HỢP ......................................................................................................... 2
I. SƠ LƯỢC VỀ TỔ HỢP ........................................................................................................... 2
1. Khái niệm ............................................................................................................................. 2
2. Các bài toán thường gặp ....................................................................................................... 2
II. NHẮC LẠI LÝ THUYẾT TẬP HỢP ...................................................................................... 2
1. Các khái niệm và ký hiệu ..................................................................................................... 2
2. Các phép toán trên tập hợp ................................................................................................... 3
III. CÁC NGUYÊN LÝ CƠ BẢN .............................................................................................. 3
1. Nguyên lí cộng ..................................................................................................................... 3
2. Nguyên lí nhân ..................................................................................................................... 4
3. Nguyên lí bù trừ .................................................................................................................... 6
4. Nguyên lí Dirichlê ................................................................................................................ 7
IV. MỘT SỐ CẤU HÌNH TỔ HỢP THƯỜNG GẶP ................................................................ 8
1. Hoán vị ................................................................................................................................. 8
2. Chỉnh hợp lặp chập k của n phần tử ..................................................................................... 8
3. Chỉnh hợp không lặp chập k của n phần tử .......................................................................... 9
4. Tổ hợp k của n phần tử ......................................................................................................... 9
PHẦN 2. PHƯƠNG PHÁP SINH ...................................................................................................... 12
I. Khái niệm ............................................................................................................................... 12
II. Áp dụng phương pháp sinh vào giải một số bài toán ............................................................. 13
1. Bài toán 1: Liệt kê tất cả các dãy nhị phân có độ dài N. .................................................... 13
2. Bài toán 2: Liệt kê các tập con m phần tử của tập n phần tử.............................................. 14
3. Bài toán 3: Liệt kê các hoán vị của tập n phần tử. ............................................................. 17
4. Bài toán 4: Phân tích số nguyên dương N thành tổng các số nguyên không âm. .............. 19
PHẦN 3. GIẢI THUẬT QUAY LUI ................................................................................................. 23
1. Ý tưởng ............................................................................................................................... 23
2. Thuật toán quay lui ............................................................................................................. 26
3. Các ví dụ ............................................................................................................................. 30
4. Tài liệu tham khảo. ............................................................................................................. 49
PHẦN IV. ỨNG DỤNG ..................................................................................................................... 52
I. KHÁI NIỆM BÀI TOÁN TỐI ƯU. ....................................................................................... 52
1. Một số khái niệm ................................................................................................................ 52
2. Một số phương pháp giải bài toán tối ưu tổ hợp ................................................................ 55
II. PHƯƠNG PHÁP DUYỆT GIẢI BÀI TOÁN TỐI ƯU ......................................................... 56
1. Tư tưởng chủ đạo ................................................................................................................ 56
2. Cấu trúc của phương pháp duyệt tổng quát ........................................................................ 56
III. BÀI TẬP ÁP DỤNG .......................................................................................................... 58
KẾT LUẬN ........................................................................................................................................ 88
TÀI LIỆU THAM KHẢO .................................................................................................................. 89
MỤC LỤC .......................................................................................................................................... 90