Tải bản đầy đủ (.doc) (13 trang)

Chuyên đề tham lam ngôn ngữ C++

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 (156.4 KB, 13 trang )

CHUYÊN ĐỀ: THUẬT TOÁN THAM LAM
Nhóm 3:Nguyễn Hồng Hoa(trưởng nhóm)
Phạm Bằng Hữu(trợ lý)
Nguyễn Phan Quỳnh Trang
Nguyễn Thị Phương Thảo
Đỗ Tuấn Trung
Đào Hữu Huy
Nguyễn Huy Đức
Trần Văn Hùng

Mục lục
Mục lục....................................................................................................................................................................1
A. Lý thuyết chung..................................................................................................................................................2
1. Khái niệm.........................................................................................................................................................2
2.Đặc điểm của chiến lược tham lam:.................................................................................................................2
3.Tính chất lựa chọn tham lam............................................................................................................................3
4. Mô tả tổng quát của thuật toán.........................................................................................................................3
5.Ví dụ áp dụng:...................................................................................................................................................4
6. Đánh giá chung:..............................................................................................................................................5
B. Bài tập.................................................................................................................................................................5
Bài toán 1: VATSUA............................................................................................................................................5
Bài toán 2: THANGMAY....................................................................................................................................7
Bài toán 3: CHENDAU.......................................................................................................................................8
Bài toán 4: OTO.................................................................................................................................................10

Trang 1


A. Lý thuyết chung
1. Khái niệm
Thuật toán tham lam là thuật toán luôn luôn tìm kiếm lựa chọn tối ưu ở thời điểm hiện tại để


đưa ra kết quả tối ưu. Điều này có nghĩa rằng, sự lựa chọn tốt nhất ở mỗi bước sẽ dẫn tới lời
giải tối ưu nhất

Ví dụ: Bài toán trả tiền của máy rút tiền tự động ATM.
Trong máy rút tiền tự động ATM, ngân hàng đã chuẩn bị sẵn các loại tiền có mệnh giá
100.000 đồng, 50.000 đồng, 20.000 đồng và 10.000 đồng. Giả sử mỗi loại tiền đều có số lượng
không hạn chế. Khi có một khách hàng cần rút một số tiền n đồng (tính chẵn đến 10.000 đồng,
tức là n chia hết cho 10000). Hãy tìm một phương án trả tiền sao cho trả đủ n đồng và số tờ giấy
bạc phải trả là ít nhất.
Gọi X = (X1, X2, X3, X4) là một phương án trả tiền, trong đó X1 là số tờ giấy bạc mệnh
giá 100.000 đồng, X2 là số tờ giấy bạc mệnh giá 50.000 đồng, X3 là số tờ giấy bạc mệnh giá
20.000 đồng và X4 là số tờ giấy bạc mệnh giá 10.000 đồng. Theo yêu cầu ta phải có X1 + X2 +
X3 + X4 nhỏ nhất và X1 * 100.000 + X2 * 50.000 + X3 * 20.000 + X4 * 10.000 = n.
Áp dụng “tinh thần” tham lam để giải bài toán này là: để có số tờ giấy bạc phải trả (X1 +
X2 + X3 + X4) nhỏ nhất thì các tờ giấy bạc mệnh giá lớn phải được chọn nhiều nhất.
Trước hết ta chọn tối đa các tờ giấy bạc mệnh giá 100.000 đồng, nghĩa là X1 là số nguyên
lớn nhất sao cho X1 * 100.000 ≤ n. Tức là X1 = n DIV 100.000.
Xác định số tiền cần rút còn lại là hiệu n – X1 * 100000 và chuyển sang chọn loại giấy
bạc 50.000 đồng…
Ví dụ khách hàng cần rút 1.290.000 đồng (n = 1290000), phương án trả tiền như sau:
X1 = 1290000 DIV 100000 = 12.
Số tiền cần rút còn lại là 1290000 – 12 * 100000 = 90000.
X2 = 90000 DIV 50000 = 1.
Số tiền cần rút còn lại là 90000 – 1 * 50000 = 40000.
X3 = 40000 DIV 20000 = 2.
Số tiền cần rút còn lại là 40000 – 2 * 20000 = 0.
X4 = 0 DIV 10000 = 0.
Ta có X = (12, 1, 2, 0), tức là máy ATM sẽ trả cho khách hàng 12 tờ 100.000 đồng, 1 tờ
50.000 đồng và 2 tờ 20.000 đồng.


2.Đặc điểm của chiến lược tham lam:
Phương pháp tham lam gợi ý chúng ta tìm một trật tự hợp lí để duyệt dữ liệu nhằm đạt được
mục tiêu một cách chắc chắn và nhanh chóng. Thông thường, dữ liệu được duyệt theo một trong
Trang 2


hai trật tự là tăng hoặc giảm dần theo một chỉ tiêu nào đó.
Giải thuật cho những bài toán tối ưu thường đi qua một số bước, với một tập hợp các chọn
lựa tại mỗi bước. Phương pháp tham lam luôn chọn phương án tốt nhất vào thời điểm hiện tại.
Nó chọn tối ưu cục bộ với hy vọng rằng lựa chọn này sẽ dẫn đến một kết quả tối ưu toàn cục.
Đối với một số bài toán, đây có thể là một thuật toán không chính xác.

3.Tính chất lựa chọn tham lam
Lựa chọn của thuật toán tham lam có thể phụ thuộc vào các lựa chọn trước đó. Nhưng nó
không thể phụ thuộc vào một lựa chọn nào trong tương lai hay phụ thuộc vào lời giải của các
bài toán con. Thuật toán tiến triển theo kiểu thực hiện các chọn lựa theo một vòng lặp, cùng lúc
đó thu nhỏ bài toán đã cho về một bài toán con nhỏ hơn.

4. Mô tả tổng quát của thuật toán
Khác với quy hoạch động, thường giải quyết các bài toán con từ dưới lên, một chiến lược
(thuật toán) tham lam thường tiến triển theo cách từ trên xuống, phương án X được xây dựng
bằng cách lựa chọn từng thành phần xi của X cho đến khi hoàn chỉnh (đủ n thành phần). Với
mỗi xi, ta sẽ chọn xi tối ưu. Với cách này thì có thể ở bước cuối cùng ta không còn gì để chọn mà
phải chấp nhận một giá trị cuối cùng còn lại.
Mô tả thuật toán:
void Greedy_Method(A,X);
// Xây dựng phương án X từ tập các đối tượng A
{
X= φ ;
While (A != φ )

{
x= Select(A); // Hàm chọn x tốt nhất trong A
A=A-{x};
If (X∪{x}) chấp nhận được X=X∪{x};
}
Return (X);
// phương án tối ưu
}
Ta có thể dễ dàng thấy tại sao các thuật toán như thế được gọi là “tham lam”. Tại mỗi bước, nó
chọn “miếng ngon nhất” (được xác định bởi hàm chọn), nếu thấy có thể đưa vào nghiệm nó sẽ
chọn ngay, nếu không nó sẽ bỏ đi, sau này không bao giờ xem xét lại.
Cần lưu ý: thuật toán tham lam trong một số bài toán, nếu xây dựng được hàm chọn thích hợp
có thể cho nghiệm tối ưu. Trong nhiều bài toán, thuật toán tham lam chỉ tìm được nghiệm gần
đúng với nghiệm tối ưu.

Trang 3


5.Ví dụ áp dụng:
Giải quyết bài toán trả tiền của máy rút tiền tự động ATM như đã nêu ở trên
Input : - Có n loại tiền, mỗi loại tiền có giá trị tương ứng là d1,d2,..,dn.
- Lượng tiền M đồng.
Output : Đổi M đồng ra tiền lẻ sao cho số loại tiền đổi là ít nhất.
*Phân tích : Cần phải tìm 1 nghiệm X = (x 1,x2,..xm) với xi là số loại tiền thứ i có giá trị d i sao
m

cho M= ∑ xi d i ⇒ Tìm cách đổi sao cho tổng loại tiền cần đổi là ít nhất. Vậy ta sẽ bắt đầu đổi từ
i =1

đồng có giá trị lớn nhất và cứ giảm dần cho đến khi số tiền M đã được đổi hết thì thông báo là

tìm được nghiệm, ngược lại thì thông báo không đổi được.
Áp dụng tư tưởng của thuật toán tham lam, ta có thể viết như sau:
void Đổitiền_Thamlam(M);
{
Const int D={d1,d2,..,dn}
// mảng lưu giá trị từng loại tiền
int x, sum, i;
// x là nghiệm bài toán
- Sắp xếp D giảm dần
X= φ ; sum:= φ ; i:=1;
While (sum!=M && i <=n) // Trong khi chưa đổi hết tiền
{
xi= (M - sum)/di ; // Số tờ tiền loại di
if (sum+xi*di ≤ M)
{
X=X+{xi};
sum:=sum+xi*di; // Số tiền đã đổi được tính đến loại tiền i
}
i++;
}
If (sum=M ) Return(X); // Trả lại nghiệm của bài toán
Else <Thông báo không đổi được>;
}
Nhận xét: - Tính chất tham lam thể hiện ở chỗ, tại mỗi bước luôn chọn “miếng ăn ngon nhất”
mà không để ý hậu quả sau này. Cho nên, với một số trường hợp thuật toán cho ta nghiệm tối
ưu, nhưng trong nhiều trường hợp nghiệm tìm được không phải là nghiệm tối ưu mà chỉ gần
đúng với nghiệm tối ưu, thậm chí còn không tính được nghiệm đúng.
Ví dụ: Nếu M=13 và các loại tiền có mệnh giá là: d 1=3, d2=4, d3=6. Cần tìm cách đổi 13 đồng
sao cho số tiền đổi là ít nhất. Với thuật toán trên ta cần 2 tờ 6 đồng dư 1 đồng. Như vậy số tiền
này sẽ không đổi được, nhưng trong thực tế, ta có thể đổi được dễ dàng với 1 tờ 6 đồng, 1 tờ 4

Trang 4


đồng và một tờ 3 đồng.

6. Đánh giá chung:
*Ưu điểm:
- Các thuật toán tham lam (greedy algorithm) nói chung là đơn giản và hiệu quả (vì các tính
toán để tìm ra quyết định tối ưu địa phương thường là đơn giản).
- Có nhiều thuật toán được thiết kế theo phương pháp tham lam cho ta nghiệm tối ưu, chẳng hạn
thuật toán Dijkstra tìm đường đi ngắn nhất từ một đỉnh tới các đỉnh còn lại trong đồ thị định
hướng, các thuật toán Prim và Kruskal tìm cây bao chùm ngắn nhất trong đồ thị vô hướng,...
- Một ứng dụng quan trọng: mô hình của các chuẩn nén dữ liệu.
* Nhược điểm: các thuật toán tham lam có thể không tìm được nghiệm tối ưu, nói chung nó chỉ
cho ra nghiệm gần tối ưu, nghiệm tương đối tốt.
- Khó để chứng minh thuật toán cho giải pháp tối ưu

B. Bài tập
Bài toán 1: VATSUA
Vào một buổi sáng anh Bo sắp một đàn bò gồm n con bò để vắt sữa. Anh dự kiến là vào sáng
hôm đó, con bò thứ i có khả năng sẽ vắt được a i lít sữa. Tuy nhiên đàn bò của anh có đặc tính là
cứ mỗi lần vắt sữa một con, những con còn lại trông thấy sợ quá nên sẽ bị giảm sản lượng mỗi
con 01 lít sữa. Nếu vắt sữa con bò thứ nhất, n-1 con còn lại bị giảm sản lượng. Sau đó vắt sữa
con bò thứ hai thì n-2 con còn lại bị giảm sản lượng.... Bạn hãy giúp anh Bo tính xem thứ tự vắt
sữa bò như thế nào để số lượng sữa vắt được là nhiều nhất nhé.
Dữ liệu vào: gồm 2 dòng
- Dòng thứ nhất là số nguyên n (1 ≤ n ≤ 100) là số lượng con bò.
- Dòng thứ hai gồm n số nguyên a1, a2,..., an (1 ≤ ai ≤ 1000) là sản lượng sữa của các con bò.
Dữ liệu xuất:
- Là một số nguyên xác định số lít sữa nhiều nhất mà anh Bo có thể vắt được.

Ví dụ
input
4
4444
output
Trang 5


10
input
4
2143
output
6
- Trong test 1: vắt sữa con bò 1 (được 4), lượng sữa còn lại là 3 3 3; vắt sữa con bò 2 (được 3),
lượng sữa còn lại là 2 2, vắt sữa con bò 3 (được 2) và con bò 4 (được 1), tổng cộng 10.
- Trong test 2: vắt sữa con bò 1 (được 2), lượng sữa còn lại là 0 3 2; vắt sữa con bò 3 (được 3)
và vắt sữa con bò 4 (được 1) tổng cộng 6.
Ý tưởng thuật toán:
Ta sử dụng thuật toán là tham lam, ở mỗi thời điểm ta chọn con bò còn
nhiều sữa nhất để vắt.
Đầu tiên, ta sắp xếp theo chiều giảm dần số lít sữa mà mỗi con bò có thể
vắt được.
Sau đó ta vắt từ con bò có nhiều sữa nhất đến khi không còn con bò nào có
thể vắt sữa, mỗi lần vắt sữa thì những con bò còn lại giảm đi 1 lít sữa.
Độ phức tạp: O(nlogn)
Code tham khảo:
#include <bits/stdc++.h>
using namespace std;
int n,a[1001],kq=0;

bool cmp(int p, int q)
{
return p>q;
}
int main()
{
cin >> n;
for (int i=0; i<n; i++) cin >> a[i];
Trang 6


sort (a,a+n,cmp);
for (int i=0; icout << kq;
}

Bài toán 2: THANGMAY
Có n người đang đứng chờ trước một thang máy duy nhất tại tầng trệt trong một tòa cao ốc
cao 2000 tầng, họ muốn đi đến các tầng trong tòa nhà. Các tầng của cao ốc được đánh số 1, 2, 3,
4, ..., 2000. Tầng trệt là tầng 1. Người thứ i muốn đi đến tầng ai. Thang máy chỉ chở được k
người cùng lúc. Thời gian thang máy đi từ tầng x đến tầng y là |x - y| giây. Hãy tính thời gian tối
thiểu để thang máy có thể vận chuyển hết n người đến tầng mà họ mong muốn và thang máy
quay trở lại tầng trệt. (giả sử thời gian ra vào thang máy là không đáng kể)
Dữ liệu nhập: gồm 2 dòng
- Dòng thứ nhất là 2 số nguyên n, k cách nhau một khoảng trắng (1 ≤ n, k ≤ 2000)
- Dòng thứ hai gồm n số nguyên ai, mỗi số cách nhau một khoảng trắng (2 ≤ ai ≤ 2000)
Dữ liệu xuất:
- Là một số nguyên xác định thời gian tối thiểu để đạt được mục đích.
Ví dụ
input

32
234
output
8
input
42
50 100 50 100
output
296
input
10 3
2222222222
output
8
Trong test 1:
- Lần thứ nhất chở 2 người lên tầng 3, 4 và quay lại trệt: 6 giây
Trang 7


- Lần thứ hai chở 1 người lên tầng 2 và quay lại trệt: 2 giây.
Ý tưởng thuật toán:
Sử dụng thuật toán tham lam
Sắp xếp mảng a theo chiều tăng dần
Vì mỗi lần vận chuyển chỉ được chở tối đa k người.
Để có được kết quả tối ưu, ở lần vận chuyển thứ 1 ta sẽ vận chuyển n%k
người đầu tiên trước, sau đó mỗi lần quay lại vận chuyển k người cho đến
khi vận chuyển hết.
Độ phức tạp: O(nlogn)
Code tham khảo:
#include <bits/stdc++.h>

using namespace std;
int main()
{
int i,n,k, a[2001];
cin >> n >> k;
for (i=1; i<=n; i++) cin >> a[i];
sort(a+1, a+n+1);
long kq=max(0,a[n%k]-1);
for (i=n%k+k; i<=n; i+=k)
kq=kq+a[i]-1;
cout << kq*2;
}

Bài toán 3: CHENDAU
Cho dãy số nguyên A gồm các số từ 1 -> n . Tìm cách chèn (n-1) dấu ‘+’ hoặc ‘–‘ vào
giữa các số sao cho khi tính biểu thức đó cho kết quả là S.
Input :
Cho n và S (1 ≤ n ≤ 500, |S| ≤ 125250)
Output :
Nếu có xuất ra biểu thức, không thì xuất ‘Impossible’
Ví dụ:
Input:
95
Trang 8


Output:
1+2-3+4+5+6+7-8-9
Input:
56

Output:
Impossible
Ý tưởng thuật toán:
Ở bài này ta sẽ dùng thuật toán tham lam
Trước hết ta tính tổng các số từ 1 -> n thông qua biến sum
=> sum = n*(n+1)/2
Bây giờ ta sẽ tìm cách chèn dấu ‘ – ‘ vào các số để được S.
Nhận xét ta sẽ thấy rằng khi chèn thêm dấu ‘ – ‘ vào trước một số x nào đó
thì tổng sum ban đầu của ta phải giảm đi 2x lần vì trừ một lần để triệt tiêu x
ban đầu, trừ thêm một lần cho việc đặt dấu ‘ – ‘.
Do đó khi ta chèn thêm dấu trừ vào thì sum luôn giảm chẵn lần để được S
hay nói cách khác sum – 2c = S -> sum – S = 2c là số chẵn , trong đó c là
tổng các số ta cần đặt dấu trừ trước nó.
Do đó ta có ngay một điều kiện để loại bỏ việc giải bài toán là sum – S chẵn
Bây giờ ta sẽ tìm các số sao cho tổng các số bằng c = 2c/2 = (sum – S) / 2
Thuật toán tham lam như sau : Xét từ n -> 2 nếu số nào c trừ được vẫn giữ
cho không âm thì ta cứ trừ. Đến cuối cùng nếu c khác 0 thì xem như không
có cách.
Độ phức tạp: O(n)
Code tham khảo:
#include <bits/stdc++.h>
using namespace std;
long n,s;
bool a[600]={};
int main()
{
scanf("%d%d",&n,&s);
long sum=n*(n+1)/2 , c=sum - s;
if (c%2==1)
{

Trang 9


printf("Impossible");
return 0;
}
c=c/2;
for (long i=n; i>=2; i--)
{
if (c-i>= 0)
{
a[i]=true;
c=c-i;
}
}
if (c!=0) printf("Impossible");
else
{
printf("1");
for (long i=2; i<=n; i++)
{
if (a[i]) printf("-");
else printf("+");
printf("%d",i);
}
}
return 0;
}

Bài toán 4: OTO

Một cơ sở sửa chữa ô tô có nhận n chiếc xe để sửa. Do các nhân viên làm việc quá lười nhác
nên đã đến hạn trả cho khách hàng mà vẫn chưa tiến hành sửa được chiếc xe nào. Theo hợp
đồng đã ký kết từ trước, nếu bàn giao xe thứ i quá hạn ngày nào thì sẽ phải trả thêm một khoản
tiền phạt là A[i].
Ông chủ cơ sở sửa chữa quyết định sa thải toàn bộ công nhân và thuê nhân công mới. Với lực
lượng mới này, ông ta dự định rằng để sửa chiếc xe thứ i sẽ cần B[i] ngày. Vấn đề đặt ra đối với
ông là phải lập lịch sửa tuần tự các chiếc xe sao cho tổng số tiền bị phạt là ít nhất.
Yêu cầu: Hãy lập lịch sửa xe giúp cho ông chủ cơ sở sửa chữa ô tô
Trang 10


Input:
• Dòng 1: Chứa số n (n ≤ 10000)
• Dòng 2: Chứa n số nguyên dương A[1], A[2], ..., A[n] (1 ≤ A[i] ≤ 10000)
• Dòng 3: Chứa n số nguyên dương B[1], B[2], ..., B[n] (1 ≤ B[i] ≤ 100)
Output:
• Dòng 1: Ghi số tiền bị phạt tối thiểu
• Dòng 2: Ghi số hiệu các xe sẽ tiến hành sửa chữa, theo thứ tự từ xe được sửa đầu tiên đến xe
sửa sau cùng
Ví dụ:
Input:
4
1342
3231
Output:
44
4231
Xong công việc 4 vào cuối ngày 1 => phải trả 2 * 1 = 2 .
Xong công việc 2 vào cuối ngày 3 => phải trả 3 * 3 = 9.
Xong công việc 3 vào cuối ngày 6 => phải trả 6 * 4 = 24 .

Xong công việc 1 vào cuối ngày 9 => phải trả 1 * 9 = 9 .
Vậy tổng cộng phải trả 44 .

Ý tưởng thuật toán:
Ta sửa dụng phương pháp tham lam để giải quyết bài toán như sau
Đầu tiên, ta sắp các xe lại theo A/B giảm dần, sau đó lần lượt sửa các xe
theo đúng thứ tự đó.
Chứng minh tính đúng đắn của phương pháp:
Để chứng minh cách làm trên là đúng, ta xét một lịch sửa xe bất kì, trong đó
tồn tại 2 xe i và i+1 có A[i]/B[i]Cần chứng minh khi đổi vị trí 2 xe đó thì tổng tiền phạt sẽ giảm.
Khi đổi vị trí 2 xe, chỉ có tiền phạt của 2 xe đó bị thay đổi, còn các xe trước
và sau đó vẫn giữ nguyên. Chứng minh:
Gọi S là thời gian để sửa tất cả các xe từ 1 đến i-1
Ta có:
Trang 11


X=(S+B[i])*A[i]+(S+B[i]+B[i+1])*A[i+1] là tổng tiền phạt của 2 xe i và i+1
theo thứ tự ban đầu.
Y=(S+B[i+1])*A[i+1]+(S+B[i+1]+B[i])*A[i] là tổng tiền phạt của 2 xe sau
khi hoán đổi vị trí cho nhau.
=> X-Y=B[i]*A[i+1]-B[i+1]*A[i]>0 vì A[i]/B[i]Vậy X>Y. (đpcm)
Độ phức tạp: O(nlogn)
Code tham khảo:
#include <bits/stdc++.h>
using namespace std;
#define MAX 10000+5
typedef struct {

int a, b, i; double x;
} car;
bool cmp( const car & a, const car & b ) {
return a.x < b.x;
}
car v[MAX];
int n;
int main()
{
int n;
cin >> n;
for(int i=1; i <=n; i++) cin >> v[i].a;
for(int i=1; i <=n; i++)
{
cin >> v[i].b;
v[i].x=(double) v[i].a/v[i].b;
v[i].i=i;
}
sort(v+1,v+n+1,cmp);
long long money=0, d=0;
for( int i=n; i>=1; i--)
{
d += v[i].b;
money += d*v[i].a;
}
cout << money << "\n";
Trang 12


for(int i=n; i>=1; i--) cout << v[i].i << " ";

}

Trang 13



×