3 Các bài toán nâng cao 2 3.1 Lũy thừa 2, 3, 5 2 3.2 Số hoàn thiện 6 3.3 Phân tích số lớn 12 3.4 Bâc cao 16 3.5 Lũy thừa 20 3.6 Ba lô 27 3.7 Balô đơn giản 37 3.8 Hình Vuông và Tam Giác 40 3.9 Chiều dài của giai thừa 43 3.10 Số ước chẵn lẻ 48 3.11 Operators (Toán tử) 49 3.12 Người thắng cử 58 3.13 Cặp điểm gần nhất 60 3.14 Mọi đường ngắn nhất 67 3.15 Đường đi và chu trình Euler. 71 3.16 Du hành 86
Trang 1MỤC LỤC
3 Các bài toán nâng cao 2
3.1 Lũy thừa 2, 3, 5 2
3.2 Số hoàn thiện 6
3.3 Phân tích số lớn 12
3.4 Bâc cao 16
3.5 Lũy thừa 20
3.6 Ba lô 27
3.7 Balô đơn giản 37
3.8 Hình Vuông và Tam Giác 40
3.9 Chiều dài của giai thừa 43
3.10 Số ước chẵn lẻ 48
3.11 Operators (Toán tử) 49
3.12 Người thắng cử 58
3.13 Cặp điểm gần nhất 60
3.14 Mọi đường ngắn nhất 67
3.15 Đường đi và chu trình Euler 71
3.16 Du hành 86
Trang 2Các bài toán nâng cao
3.1 Lũy thừa 2, 3, 5
Dijkstra E Xét dãy số s được sắp tăng gồm 1000 số đầu tiên là tích các lũy thừa của 2, 3 và 5, tức là các số có dạng
2a3b5c, a, b, c = 0, 1, 2, …
s = 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, … Với mỗi giá trị nguyên dương k cho trước, hãy cho biết dãy s có bao nhiêu số có trên k ước.
Chú thích
Bài nguyên gốc do Dijkstra đề xuất chỉ đòi hỏi hiển thị dãy s Chúng ta sửa lại nội dung đôi chút nhằm minh họa các thao tác xử lý ước.
Thí dụ
Với n = 1000; k = 250 ta có 9 số có trên 250 ước số
Trang 3Thuật toán
Theo đầu bài, ta hãy tạm giả thiết là dãy s gồm vô hạn các số là tích các lũy thừa 2, 3 và 5, do đó s có các tính chất sau:
Dãy s được sắp tăng;
Dãy s chứa 1 là số nhỏ nhất, ứng với 203050;
Nếu s chứa số v = 2m3n5p thì s phải chứa 3 số 2v = 2m+13n5p, 3v = 2m3n+15p và 5v = 2m3n5p+1
Do yêu cầu sắp tăng nên s sẽ có dạng sau đây:
1, , v, , 2v, , 3v, , 5v,
Chú ý rằng 2v < 3v nhưng không nhất thiết 2v phải đứng sát sau v Giả sử ta có y = 2m’3n’5p’ thuộc s và 2y =
2m’+13n’5p’ > v thì khi đó dãy s phải có dạng sau đây:
1, ,y, , v, , 2y,
Ta cũng có thể có z thuộc s và 3z > v, hoặc t thuộc s và 5t > v.
Tổng hợp lại ta thấy, nếu ta đã sinh được dãy s1 = 1, s2 = 2, ,si1 = v thì để tính phần tử si ta phải xác định được ba số s[a], s[b], s[c] trong dãy s[1 i1] sao cho:
2s[a] là giá trị đầu tiên vượt quá v, tức là 2s[a] > v;
3s[b] là giá trị đầu tiên vượt quá v, tức là 3s[b] > v;
và 5s[c] là giá trị đầu tiên vượt quá v, tức là 5s[c] > v;
Khi đó, s[i] = max{2s[a], 3s[b], 5s[c]}
Một khi đã xác định được phần tử v = 2m3n5p trong dãy s thì ta tính được ngay số ước của v là (m+1)(n+1) (p+1)
Như vậy ta cần ba biến a, b và c để theo dõi các chỉ số trong dãy s ứng với các thừa số nguyên tố 2, 3 và 5 trong dãy Ngoài mảng s, ta cần thêm 3 mảng m2, m3 và m5 để chứa các số mũ của mỗi phần tử v trong dãy s Chú ý rằng hàm Min3(x,y,z) không cho ta giá trị nhỏ nhất trong 3 số nguyên x, y và z mà cho ta vị trí chứa min là 1, 2 hoặc 3 để hiểu rằng tham biến thứ mấy trong 3 tham biến x, y, z đạt trị min Thí dụ, Min3(15, 12, 25) = 2; Min3(12,25,15) = 1; Min3(25,15,12) = 3.
Trang 4int a, b, c; // cac chi so cho 2, 3, 5
int d = 0; // dem cac so co so uoc > k
Trang 5a, b, c: int; { cac chi so cho 2, 3, 5 }
d: int; { dem cac so co so uoc > k }
begin { Khoi tri }
Trang 63.2 Số hoàn thiện
Số nguyên dương n được gọi là số hoàn thiện nếu tổng các ước của n gấp đôi n:
T(n) = 2n Thí dụ, 6 là số hoàn thiện, vì 6 có các ước 1, 2, 3 và 6, và 1+2+3+6 = 2 6 = 12.
Đây cũng là số hoàn thiện nhỏ nhất.
Hãy tìm các số hoàn thiện trong 1 triệu số nguyên dương đầu tiên.
Thuật toán
Với mỗi số n trong khoảng từ 1 1000000 ta kiểm tra hệ thức
2n = TongUoc(n)
Để tính tổng ước của một số nguyên dương theo phương pháp gián tiếp, tức là dựa vào dãy số nguyên tố p
do sàng Eratosthenes tạo ra ta phải cải tiến hàm Sieve(n) đôi chút Cụ thể là sau khi sinh các số nguyên tố từ trong khoảng 1 n ta sinh thêm một số nguyên tố q nữa đặt ở cuối dãy, dĩ nhiên q > n Số q này được gọi là lính canh Q có nhiệm vụ báo cho bạn biết giới hạn phải dừng khi bạn tổ chức các vòng lặp duyệt các số nguyên tố trong dãy p đại loại như
while (p[i] <= n) …
Để kiểm tra tính nguyên tố của số n ta dựa vào nhận xét sau.
Nhận xét
Số nguyên n > 1 là nguyên tố khi và chỉ khi n không có ước nguyên tố trong khoảng 1
Thật vậy, nếu n nguyên tố thì n không có ước khác 1 nào nhỏ thua nó và do đó n không có ước nguyên tố
nào trong khoảng 1
Đảo lại, nếu n = ab, a,b > 1 thì chí ít một trong hai số a hoặc b phải thuộc khoảng 1 Giả sử số đó là a.
Ta có a Nếu a nguyên tố thì n có ước nguyên tố a trong khoảng 1 Nếu a không phải là số nguyên tố thì a có một ước nguyên tố p nào đó Ta có ngay p < a
Giả sử ta đã có dãy số nguyên tố do hàm Sieve sinh ra là p[1] = 2; p[2] = 3; p[3] = 5,… và giả sử ta đã biết
số nguyên n > 1 không có ước nguyên tố nào từ p[1] đến p[d1] Dựa theo nhận xét trên để kiểm tra tính nguyên
tố của n ta chỉ còn phải xét tiếp các số nguyên tố từ p[d] đến Ta viết hàm Prime(n, d) kiểm tra tính nguyên
tố của n với giả thiết là n không có ước nguyên tố nào nhỏ thua p[d] như sau:
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
Trong các phần tiếp theo bạn đọc sẽ thấy tính linh hoạt khi sử dụng hàm Prime(n, d) trong tính toán.
Tiếp theo ta cải tiến hàm TongUoc1(n) tính tổng các ước của n theo tiếp cận gián tiếp dựa trên dãy số nguyên tố p như sau:
Lần lượt duyệt các số nguyên tố trong dãy p cho đến , nếu gặp số nguyên tố pi là ước của n thì tính tổng của thừa số pim theo hàm Tong(n, pi) trong đó m là bậc của p[i] trong n Nhắc lại rằng hàm này đồng thời chia liên tiếp n cho pi và do dó làm thay đổi trị của n, cụ thể là sau khi gọi hàm Tong(n, pi) ta sẽ thu được trị mới của
n là n’ = n/ pim Nếu n’ = 1 thì ta kết thúc hàm Nếu n’ là nguyên tố thì trong n, n’ có bậc 1 Vậy khi n’ nguyên
tố thì tổng các ước của n’ sẽ là n’+1
Trang 7Ta cũng nhận thấy rằng do n được chia liên tiếp cho các ước nguyên tố không vượt quá pi nên n’ không còn ước nào trong khoảng từ p1 đến pi Nhận xét này giúp ta thu gọn thủ tục kiểm tra tính nguyên tố của n’ như sau:
ta chỉ cần xét tiếp xem n’ có ước nguyên tố nào trong khoảng từ pi+1 đến là đủ
// Tong cac uoc cua n: gian tiep qua Sieve
const int Range = 1000000;
int p[MN+2]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
int can = int(sqrt(n)); // can bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
j = 0;
Trang 8for (i = 2; i <= n; ++i)
if (p[i] == PRIME) p[++j] = i;
// Them 1 so nguyen to cuoi day
// lam linh canh
// n thay doi sau khi goi ham
int Tong(int& n, int q){
int t = 0, u, can = int(sqrt(n));
for (u = 1; u <= can; ++u)
if (n % u == 0)
t += u + n/u;
return (can*can == n) ? t - can : t;
}
// Cac so hoan thien trong khoang 1 1000000
// Gian tiep qua Sieve
Trang 9(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
Trang 10Them 1 so nguyen to > n vao cuoi day
lam linh canh *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
var i, j, can: Li;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
n thay doi sau khi goi ham *)
function Tong(var n: Li; q: Li): Li;
(* Tong cac uoc cua n: gian tiep qua Sieve *)
function TongUoc1(n: Li): Li;
var i, t, can: Li;
Trang 11function TongUoc2(n: Li): Li;
var t, u, can: Li;
begin
t := 0; can := round(sqrt(n));
for u := 1 to can do
if (n mod u = 0) then t := t+u+(n div u);
if (can*can = n) then TongUoc2 := t-can
else TongUoc2 := t;
end;
(* Cac so hoan thien trong khoang 1 1000000
Gian tiep qua Sieve *)
function Perfect1: Li;
Trang 12Kết quả
3.3 Phân tích số lớn
Mục này trình bày một thuật toán cải tiến để có thể phân tích nhanh một số lớn ra thừa số nguyên tố Chúng
ta cần đáp ứng được hai tiêu chí tưởng như đối nghịch nhau sau đây:
Chỉ đòi hỏi Sieve sinh các số nguyên tố trong khoảng 1 150 ngàn, nhưng
xong gian tiep
Thoi gian tinh: 6
Truc tiep: 6 28 496 8128
Tong cong: 4 so
Thoi gian tinh: 9
Trang 13 Bước 1 Gọi hàm Sieve(MN) để nhận các số nguyên tố trong khoảng 1 MN, với MN = 150000 Để
ý rằng (15.104)2 = 225.108 vượt quá giới hạn của số nguyên dương 32 bit
Bước 2 Lần lượt xét các số nguyên tố p[i] trong khoảng 1 và với điều kiện n > 1: Nếu n chia hết cho p[i] thì gọi hàm CDegree(n, p[i]) để xác định bậc của p[i] trong n đồng thời tính lại gía trị n
int p[MN+1]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
int can = int(sqrt(n)); // can bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
j = 0;
for (i = 2; i <= n; ++i)
if (p[i] == PRIME) p[++j] = i;
// Them 1 so nguyen to cuoi day
// lam linh canh
// Bac cua thua so v trong n
int CDegree(int& n, int v){
Trang 14return d;
}
// Phan tich a ra thua so nguyen to
void Decompose2(int a){
if (a == 1) return;
int can = int(sqrt(a));
int i, m; // m la bac cua p[i] trong n
for (i = 1; a > 1 && p[i] <= can; ++i){
cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
var p: TA; { Day so nguyen to do Sieve sinh }
(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
Trang 15(* The Sieve of Eratosthenes
Them 1 so nguyen to > n vao cuoi day
lam linh canh *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
{ Bac cua uoc nguyen to q trong n
n thay doi sau khi goi ham }
function CDegree(var n: Li; q: Li): Li;
procedure Decompose2(a: Li);
var i,m,can: Li;
Trang 17Ta thực hiện lại thuật toán trong bài 3.3 với chỉnh sửa nhỏ sau đây: Thay vì phân tích ra thừa số ta chỉ lấy số
int p[MN+1]; // Day so nguyen to
// Kiem tra tinh nguyen to cua n
// bang cach duyet day so ng to
// tu p[d]
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
if (n % p[i] == 0) return 0;
}
return 1;
}
// The Sieve of Eratosthenes
// Them 1 so nguyen to > n vao cuoi day
// lam linh canh.
int Sieve(int n) {
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
int can = int(sqrt(n)); // can bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
j = 0;
for (i = 2; i <= n; ++i)
if (p[i] == PRIME) p[++j] = i;
// Them 1 so nguyen to cuoi day
// lam linh canh
// Bac cua thua so v trong n
int CDegree(int& n, int v){
Trang 18return (a >= b) ? a : b;
}
// Bac cao nhat cua uoc nguyen to cua a
int MaxDegree(int a){
cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
var p: TA; { Day so nguyen to do Sieve sinh }
(* Kiem tra tinh nguyen to cua n
bang cach duyet day so ng to tu p[d] *)
function IsPrime(n, d: Li): Boolean;
var can, i: Li;
Trang 19end;
IsPrime := true;
end;
(* The Sieve of Eratosthenes
Them 1 so nguyen to > n vao cuoi day
lam linh canh *)
function Sieve(n: Li): Li;
const PRIME = 0; DELETED = 1;
(* Them 1 so nguyen to cuoi day
lam linh canh *)
{ Bac cua uoc nguyen to q trong n
n thay doi sau khi goi ham }
function CDegree(var n: Li; q: Li): Li;
function MaxDeg(a: Li): Li;
var i, can, maxm: Li;
Trang 20maxm := Max(maxm, CDegree(a,p[i]));
inc(i);
end;
if (a > 1) then Maxdeg := Max(maxm,1)
else Maxdeg := maxm;
Cho số nguyên dương a Hãy tìm số nguyên dương nhỏ nhất n thỏa điều kiện nn chia hết cho a.
Dữ liệu vào gồm k test như sau:
Dòng đầu tiên: số test k.
Trang 21Mỗi dòng tiếp theo là một số a, giá trị tối đa là 1 tỷ.
Dữ liệu ra gồm k dòng, mỗi dòng một số n là kết quả tìm được theo test tương ứng
312859154 872825381 24
Thuật toán
Phương pháp 1 Duyệt toàn bộ
for (n = 1; n <= a; ++i)
if (Pow(n) % a == 0) Ghi n ra output file;
Phương pháp này chỉ thích hợp với dữ liệu nhỏ.
Để tính z = am ta dùng phương pháp chia để trị như sau:
Giả sử m = 2k + r, 0 r 1/ Khi đó,
nếu r = 0, tức là m là số chẵn thì z = (a2)k, ngược lại, nếu r = 1, tức là m là số lẻ thì z = a(a2)k.
Dựa vào công thức trên ta viết hàm Pow(x) tính z = xx theo phương pháp chia để trị như sau:
int PowMod(int x, int a){
int z = 1, m = x; // Tinh z = x^m mod a
Trang 22Phương pháp 2 Giả sử a được phân tích ra thừa số nguyên tố tức là a là tích các số nguyên tố đôi một khác
nhau pi với số mũ tương ứng mi
Đặt t = p1p2…pk, và gọi m là giá trị lớn nhất trong các số mũ mi , i = 1 k
Nhận xét 1 Mọi số nguyên dương b chia hết cho a khi và chỉ khi dạng phân tích ra thừa số của b chứa mọi thừa
số nguyên tố pi với số mũ không nhỏ thua mi, i = 1,2,…,k
Nhận xét 2 Nếu t m thì n = t chính là số nhỏ nhất chia hết a.
Thật vậy, vì t = p1p2…pk nên khi đó nn = tt đáp ứng điều kiện trong nhận xét 1
Nhận xét 3 Nếu t < m thì tồn tại một số i nhỏ nhất để n = it chính là số nhỏ nhất để nn chia hết a.
Từ các nhận xét trên ta xây dựng được thuật toán sau:
Bước 1 Phân tích a ra thừa số nguyên tố Gọi p1, p2, …, pk là các thừa số nguyên tố trong dạng phân
tích của a Tính t = p1p2…pkvà tính m là giá trị lớn nhất trong các số mũ mi , i = 1, 2, , k.
Bước 2 Nếu t m thì lấy kết quả n = t, nếu không thì tìm i đầu tiên thỏa điều kiện (it)it chia hết a, lấy kết quả n = it.
for (i = 2; PowMod(i*t,a) != 0; ++i);
const int PRIME = 0;
const int DELETED = 1;
int i, j;
memset(p,PRIME,sizeof(p));
int can = int(sqrt(n)); // can bac 2 cua n
for (i = 2; i <= can; ++i)
if (p[i] == PRIME)
for (j = i*i; j <= n; j += i) p[j] = DELETED;
// Dua cac so nguyen to ve dau mang p
// Bac cua thua so v trong n
int CDegree(int& n, int v){
Trang 23return d;
}
// Kiem tra n = 1 ? hoac n nguyen to?
// n chia het cho mot trong cac so ng to p[d c]
//
int Prime(int n, int d){
int can = int(sqrt(n));
for (int i = d; p[i] <= can; ++i){
int Power(int a){
int maxm = 0, m, can = int(sqrt(a));
Trang 24cout << "\n So test = " << soTest;
for (i = 1; i <= soTest; ++i){
type int = longint;
var p: array[0 MN+1] of int; { Day so nguyen to }
{ The Sieve of Eratosthenes }
function Sieve(n: int): int;
const PRIME = 0; DELETED = 1;
{ Bac cua thua so v trong n }
function CDegree(var n: int; v: int): int;
Trang 25Kiem tra n = 1 ? hoac n nguyen to?
n chia het cho mot trong cac so ng to p[d c]
-}
function Prime(n, d: int): Boolean;
var i, can: int;
function Power(a: int): int;
var maxm, m, inita, can, t, i: int;
i := 1; t := 1; { t : tich cac p[i] }
while (p[i] <= can) do
Trang 26writeln(nl, ' Test no ', i, ' a = ', a);
writeln( ' Result = ', Power(a));
write(nl,' Press any key to continue '); readln;
Trang 27Dãy thứ hai: (a[2], a[3], a[5]) = (1, 2, 3);
Dãy thứ ba: (a[2], a[4], a[5], a[6]) = (1, 1, 3, 1);
Dãy thứ tư: (a[3], a[4], a[5]) = (2, 1, 3);
Dãy thứ năm: (a[3], a[5], a[6]) = (2, 3, 1)
Thuật toán
Bài toán Ba lô hiện mới có thuật toán với độ phức tạp cỡ 2n, nghĩa là để tìm nghiệm ta phải duyệt mọi tập
con các chỉ số của n chỉ số Dưới đây sẽ khảo sát hai tiếp cận: tiếp cận quay lui và tiếp cận nhánh cận.
Quay lui
Ta sử dụng một ngăn xếp x để lưu chỉ số của các vật được chọn vào ba lô Với thí dụ đã cho thì nghiệm thứ
ba sẽ là x[1 4] = (2, 4, 5, 6), với ý nghĩa là có 4 vật được chọn vào ba lô là vật 2, 4, 5 và 6 Thuật toán là một vòng lặp gồm hai pha.
Pha tiến: Duyệt từ vật i = k+1 n để chọn và xếp vào ba lô các vật i sao cho ba lô có trọng lượng s
không vượt quá sức chứa m
Tại cuối pha này xét điều kiện s = m? Nếu đạt thì chấp nhận thêm 1 nghiệm.
Pha lùi: Dỡ khỏi ba lô vật trên cùng, ghi nhận số hiệu vật này vào biến k
Để theo dõi quá trình thực hiện của chương trình ta dùng biến logic show dùng để điều khiển việc hiển thị Nếu show = 1 thì chương trình sẽ hiển thị từng nghiệm tìm được; Bạn muốn dừng hiển thị thì bấm 's', khi đó biến show sẽ nhận trị 0
Chương trình C++
// Quay lui
// a[1 ns] : trong luong tung vat, m : suc chua cua ba lo
int Balo1(int a[], int n, int m){
int s = 0; // Tong hien co
int i;
int j = 0; // ngon stack x
int k = 0; // se duyet tu vat thu k+1 tro di
Trang 28int d = 0; // dem so nghiem
j: Li; { ngon stack x }
k: Li; { se duyet tu vat thu k+1 tro di }
d: Li; { dem so nghiem }
write(nl, ' Muon dung in bam s: ');
if (readkey = 's') then show := false;
Trang 29Chúng ta có thể cải tiến chút ít thủ tục quay lui nói trên Bạn để ý rằng vật i được chọn vào ba lô x khi và chỉ khi tổng trọng lượng hiện hành của ba lô s + a[i] m Như vậy, nếu v vật trên cùng của ba lô lần lượt là v vật cuối của dãy a, tức là nv+1, nv+2,…,n1, n thì trong pha 2 ta có thể dỡ ngay v vật này khỏi ba lô, vì sau đó, nếu quay lại pha 1 tại vòng lặp tiếp theo ta chỉ có thể nhận được trọng lượng nhỏ thua m Cải tiến này được đưa vào thủ tục Balo12 như sau:
Chương trình C++
// Quay lui
int Balo12(int a[], int n, int m){
int s = 0; // Tong hien co
int i;
int j = 0; // ngon stack x
int k = 0; // se duyet tu vat thu k+1 tro di
int d = 0; // dem so nghiem
j: Li; { ngon stack x }
k: Li; { se duyet tu vat thu k+1 tro di }
d: Li; { dem so nghiem }
Trang 30write(nl, ' Muon dung in bam s: ');
if (readkey = 's') then show := false;
Như vậy ta cần tính trước các giá trị t[i] = tổng trọng lượng của các vật từ i đến n Ta gọi t[i] là trọng lượng của hậu tố i
Phương án 2 khá giống với phương án 1 ngoại trừ hai chi tiết quan trọng sau đây:
1 Xét điều kiện s+t[k+1] m trước khi quyết định có duyệt tiếp dãy các vật từ k+1 đến n.
2 Khi dỡ các vật trên cùng của ngăn xếp là n, n-1, …, n-i ta không phải giảm ngay trọng lượng ba lo vì ta đã tính trước các giá trị t[i] = a[i]+a[i+1]+…+a[n] Việc giảm này sẽ thực hiện vào cuối vòng lặp chỉ bằng 1 phép trừ, cụ thể là s = s t[i]
Chương trình C++
int Balo2(int a[], int n, int m){
int s = 0; // Tong hien co
int i;
int j = 0; // ngon ngan xep x
int k = 0;
int d = 0; // dem so nghiem
// t[i] = tong a[i n]
Trang 31j: Li; { ngon stack x }
k: Li; { se duyet tu vat thu k+1 tro di }
d: Li; { dem so nghiem }
Trang 32Test1 của chương trình dưới đây thao tác trên dãy a gồm 6 phần tử cho sẵn như ví dụ ở đầu bài, Test2 sinh
tự động dãy a với n = 30 phần tử và sức chứa m của ba lô để đối sánh hai tiếp cận quay lui và nhánh cận.
int x[MN]; // stack nghiem
int t[MN]; // tong sau
int show = 1;
// sinh ngau nhien n phan tu
// mang gia tri 1 r cho mang a
int Gen(int a[], int n, int r){
// Hien thi a[d c]
void Print(int a[], int d, int c, const char * msg = ""){
int Balo12(int a[], int n, int m){
int s = 0; // Tong hien co
int i;
int j = 0; // ngon stack x
int k = 0; // se duyet tu vat thu k+1 tro di
int d = 0; // dem so nghiem
Trang 33int Balo2(int a[], int n, int m){
int s = 0; // Tong hien co
int i;
int j = 0; // ngon ngan xep x
int k = 0;
int d = 0; // dem so nghiem
// t[i] = tong a[i n]
Trang 34int s2 = Balo2(a,n,m);
cout << "\n Tong cong " << s2 << " nghiem.";
cout << "\n\n Phuong an 1: Quay lui: \n ";
cout << "\n Tong cong " << s2 << " nghiem.";
cout << "\n Thoi gian: " << difftime(t2,t1) << ".";
cout << "\n\n Phuong an 1: Quay lui: \n ";
var a: TA; { khoi luong cac vat }
x: TA; { ngan xep - balo }
t: TA; { t[i] = tong khoi luong cac vat i n }
show: Boolean;
{ Sinh ngau nhien n gia tri 1 r cho mang a }
function Gen(var a: TA; n, r: Li): Li;
Trang 35if random(2) = 1 then m := m + a[i];
end;
Gen := m;
end;
{ Hien thi a[d c] }
procedure Print(var a: TA; d,c: Li);
j: Li; { ngon stack x }
k: Li; { se duyet tu vat thu k+1 tro di }
d: Li; { dem so nghiem }
j: Li; { ngon stack x }
k: Li; { se duyet tu vat thu k+1 tro di }
d: Li; { dem so nghiem }
begin
s := 0; j := 0; d := 0; k := 0;
Trang 36{ Tinh cac tong rieng }
{ Thoi gian (phan tram giay) }
function Time: Li;
var gio, phut, giay, g100: word;
a[1] := 6; a[2] := 1; a[3] := 2;
a[4] := 1; a[5] := 3; a[6] := 1;
writeln(' Tong cong ', s2, ' nghiem ');
writeln(' Phuong an 1: Quay lui: ');
s1 := Balo12(a,n,m);
writeln(' Tong cong ', s1, ' nghiem.');
end;
procedure Test2;
Trang 37writeln(' Tong cong ', s2, ' nghiem ');
writeln(' Thoi gian: ', (t2-t1) div 100, ' giay ');
writeln(' Phuong an 1: Quay lui: ');
t1 := Time;
s1 := Balo12(a,n,m);
t2 := Time;
writeln(' Tong cong ', s1, ' nghiem.');
writeln(' Thoi gian: ', (t2-t1) div 100, ' giay ');
3.7 Balô đơn giản
Nếu mảng a[1 N] chứa khối lượng các vật tạo thành một dãy số siêu tăng, tức là thoả tính chất: mỗi phần tử
không nhỏ hơn tổng các phần tử đứng trước nó thì ta nên dùng thuật toán đơn giản giống như là "rót nước" như
sau.
Hãy coi ba lô như một bình đựng nước rỗng b có sức chứa M Duyệt ngược, lần lượt lấy các cốc nước từ a[N] đến a[1] đổ vào bình b Nếu đổ lượng nước từ cốc a[i] vào bình mà không làm tràn bình thì chọn cốc đó và đánh dấu x[i] = 1 Nếu nước tràn thì bỏ qua cốc a[i] này Sau khi duyệt, nếu bình b còn vơi thì bài toán vô nghiệm Nếu bình b vừa đầy thì bài toán có duy nhất một nghiệm
Bảng biến thiên dưới đây giải trình hoạt động của thuật toán.
0(bỏ qua)
1(lấy)
0(bỏ qua)
0(bỏ qua)
1(lấy)
Minh hoạ thủ tục rót nước với N = 7, M = 137:
Duyệt ngược từ cốc [7] đến cốc [1]
Trang 38Dòng cuối ghi lượng nước còn có thể rót thêm vào bình b.
Bạn lưu ý rằng bài toán Balô đơn giản chỉ dẫn đến một trong 2 tình huống: có 1 nghiệm hoặc vô nghiệm.
// Hien thi doan a[d c] kem chi dan
void Print(int a[], int d, int c) {
for (int i = d; i <= c; ++i)
Trang 39{ Hien thi doan a[d c] }
procedure Print(var a: ta; d, c: int);
{ sinh ngau nhien day sieu tang }
function Gen(nn: int): int;
Trang 40if (random(5) > 2) then m := m + a[i];
a[1] := 3; a[2] := 3; a[3] := 6; a[4] := 14;
a[5] := 30; a[6] := 60; a[7] := 120;
Ta có thể bố trí các số 1 15 trong hình tam giác đều cạnh dài 5 vào hình vuông 44 khuyết một ô trống
Hãy cho biết giữa khoảng a b có bao nhiêu giá trị x, a < x+1 < b có thể đồng thời xếp kín một tam giác và hình vuông khuyết một góc?
vtg.inp output Input text file vtg.inp: mỗi dòng 1 test gồm 2 số a b trong