Tải bản đầy đủ (.docx) (70 trang)

Phương pháp quy hoạch động và một số ví dụ cụ thể

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 (437.56 KB, 70 trang )

Mục lục
LỜI MỞ ĐẦU
Trong thực tế, khi gặp một bài toán, một vấn đề cần được giải quyết, ta luôn
mong muốn tìm được phương án cho kết quả tốt nhất, tối ưu nhất. Bởi vậy, các lớp bài
toán về tối ưu hóa luôn đóng một cai trò quan trọng, vì tính ứng dụng cao trong thực
tiễn.
Đối với lớp bài toán tối ưu, có rất nhiều phương pháp để giải quyết như: phương
pháp nhánh cận, phương pháp tham lam, phương pháp quy hoạch động. Tùy từng bài
toán cụ thể mà ta sử dụng phương pháp thích hợp, nhằm đạt được hiệu quả cao nhất
(hiệu năng, tốc độ thực hiện, độ phức tạp, bộ nhớ sử dụng ). Trong đó, phương pháp
quy hoạch động (Dynamic programming) luôn được ưu tiên lựa chọn vì tính hiệu quả
cao và tối ưu hơn hầu hết các phương pháp khác.
Các bài toán quy hoạch động chiếm một vị trí khá quan trọng trong tổ chức hoạt
động và sản xuất. Chính vì lẽ đó mà trong các kì thi học sinh giỏi quốc gia và quốc tế
chúng ta thường gặp loại toán này.
Thông thường những bài toán dùng phương pháp quay lui, vét cạn cho các bài
toán quy hoạch động thì chỉ có thể vét được các tập dữ liệu nhỏ, kích thước chừng vài
chục byte. Nếu tìm được đúng hệ thức thể hiện bản chất quy hoạch động của bài toán
và khéo tổ chức dữ liệu thì ta có thể xử lí được những tập dữ liệu khá lớn.
Phương pháp quy hoạch động là phương pháp giải quyết tốt các bài toán tối ưu,
đồng thời khi áp dụng giải một số bài toán không tối ưu cũng cho ra kết quả rất tốt.
Tuy nhiên, việc áp dụng phương pháp quy hoạch động không phải dễ, vì mỗi bài toán
có đặc điểm tính chất riêng và cách giải hoàn toàn khác nhau. Vậy một bài toán như
thế nào thì có thể áp dụng được phương pháp quy hoạch động, và liệu có phải rằng tất
cả các bài toán đều có thể giải quyết được bằng quy hoạch động?
Hy vọng trong báo cáo này, em có thể làm rõ phần nào những câu hỏi trên. Và
cho mọi người thấy được tông quan về quy hoạch động và các bài toán nào giải quyết
bằng phương pháp quy hoạch động.
CHƯƠNG 1: TỔNG QUAN VỀ QUY HOẠCH ĐỘNG
1.1 Bài toán tối ưu
1.1.1 Khái niệm


Bài toán tối ưu gồm có 1 hàm f gọi là hàm mục tiêu hay hàm đánh giá; các hàm
g
1
, g
2
, …, g
n
cho giá trị logic gọi là hàm ràng buộc. Yêu cầu của bài toán là tìm một
phương án x thoả mãn tất cả các ràng buộc g
1
, g
2
, …, g
n
và x là tốt nhất, theo nghĩa
không tồn tại một cấu hình nào khác thoả mãn các hàm ràng buộc mà f() tốt hơn f(x).
Bài toán tối ưu là bài toán thường có nhiều phương án chấp nhận được và mỗi
nghiệm có một giá trị đánh giá. Mục tiêu đặt ra là tìm ra nghiệm tối ưu, đó là phương
án có giá trị đánh giá lớn nhất hoặc nhỏ nhất (tối ưu).
1.1.2 Một số ví dụ về bài toán tối ưu
Ví dụ : Bài toán Ba lô: có một ba lô có thể chứa tối đa trọng lượng M và có n đồ
vật (n ≤ 100), mỗi đồ vật có trọng lượng w
i
và giá trị b
i
. (M, w
i
, b
i
Hãy chọn và xếp

các đồ vật vào ba lô để tổng giá trị của ba lô là lớn nhất.
Với bài toán trên ta thấy:+ Hàm mục tiêu:
i
b
i
max
+ Hàm ràng buộc :
i
p
i
M
Với p
i
=0 : đồ vật thứ I không được chọn
p
i
=1 : đồ vật thứ I được chọn.
Tóm lại, bài toán tối ưu rất phong phú, đa dạng, được ứng dụng nhiều trong thực
tế. Tuy nhiên, còn rất nhiều các bài toán tối ưu là không giải được hoặc chưa có lời
giải.
1.1.3 Nguyên lý tối ưu Bellman
Nguyên lý tối ưu của R.Bellmam được phát biểu như sau: “tối ưu bước thứ n
bằng cách tối ưu tất cả con đường tiến đến bước n-1 và chọn con đường có tổng chi phí
từ bước 1 đến bước n-1 và từ n-1 đến n là tốt nhất (nhiều nhất).
Nguyên lý này được thừa nhận mà không chứng minh.
Nguyên lý tối ưu Bellman cũng có thể phát biểu theo một cách khác như sau:
“Với mỗi quá trình điều khiển tối ưu, bắt đầu từ trạng thái A
0
. Khi xây dựng trạng
thái A

k
bất kỳ trong quá trình đó từ trạng thái A
k-1,
nếu A
k-1
là tối ưu, thì A
k
xây dụng
được sẽ tối ưu. Vậy trạng thái A
n
cuối cùng sẽ là tối ưu”.
Phương pháp tìm nghiệm tối ưu theo nguyên lý Bellman được gọi là phương
pháp quy hoạch động. Thuật ngữ này nói lên thực chất của quá trình điều khiển tôi ưu
là động: Có thể trong một số bước đầu tiên lựa chọn điều khiển tối ưu dường như
không tốt nhưng tựu chung cả quá trình lại là tốt nhất.
1.2 Phương pháp quy hoạch động
1.2.1 Phương pháp chia để trị
“Chia để trị” là phương pháp tách bài toán ban đầu thành các bài toán con độc
lập, sau đó giải các bài toán con rồi tổ hợp dần lời giải từ bài toán con nhỏ nhất đến bài
toán ban đầu. Phương pháp chia để trị là một phương pháp rất thông dụng trong tin học
và kỹ thuật tính toán.
Phương pháp chia để trị thường được áp dụng cho những bài toán có bản chất đệ
quy: bài toán P ban đầu có thể được giải bằng lời giải của bài toán con P’ có dạng
giống như P,nhưng theo một nghĩa nào đó P’ phải nhỏ hơn P, dễ giải hơn P và việc giải
nó không cần dùng đến P. Giải thuật dùng để giải bài
toán có bản chất đệ quy gọi là giải thuật đệ quy.
1.2.2 Khái niệm về quy hoạch động
a. Khái niệm
Phương pháp quy hoạch động do nhà toán học
người Mỹ Richard Bellman (1920- 1984) phát minh

năm 1953. Phương pháp này dùng để giải các bài toán
tối ưu có bản chất đệ qui, tức là tìm phương án tối ưu
cho bài toán đó có thể đưa về tìm phương án tối ưu của
một số hữu hạn các bài toán con. Cũng áp dụng phương
pháp chia để trị, nhưng so với đệ quy, quy hoạch động được cái tiến và tối ưu hơn rất
nhiều.
Phương pháp đệ quy giải quyết bài toán theo hướng top-down (từ trên xuống),
nghĩa là để giải bài toán ban đầu, ta phải đi giải các bài toán con bé hơn, và chia các
bài toán con này bé hơn nữa, đến các bài toán cơ sở. Đây là một phương pháp hay, tuy
nhiên phương pháp này sẽ gặp hạn chế về mặt thời gian, tốc độ do phải tính đi tính lại
nhiều lần một số bài toán con giống nhau nào đó.
Ngược lại, phương pháp quy hoạch động sử dụng nguyên lý bottom-up, nghĩa là
"đi từ dưới lên". Đầu tiên, ta sẽ phải giải các bài toán con cơ sở, đơn giản nhất, có thể
tìm ngay ra nghiệm. Sau đó kết hợp các bài toán con này lại để tìm lời giải cho bài toán
lớn hơn và cứ như thế cho đến khi giải được bài toán ban đầu. Với phương pháp này,
mỗi bài toán con sau khi giải xong đều được lưu trữ lại và đem ra sử dụng nếu cần. Do
đó tiết kiệm bộ nhớ và cải thiện được tốc độ.
Vậy phương pháp quy hoạch động là một kỹ thuật nhằm đơn giản hóa việc tính
toán các công thức truy hồi bằng cách lưu toàn bộ hay một phần kết quả tính toán tại
mỗi bước trước đó với mục đích sử dụng lại.
 Quy hoạch động = Chia để trị + Hướng lưu trữ (lưu lại kết quả).
b. Đặc điểm của phương pháp quy hoạch động
Khi giải một bài toán bằng cách “chia để trị”, ta chia bài toán lớn thành các bài
toán con cùng kiểu nhỏ hơn, và giải quyết bằng giải thuật đệ quy. Khi đó, trên thực tế,
nhiều kết quả trung gian phải tính lại nhiều lần, dẫn tới việc lãng phí và chậm tốc độ.
Vì vậy, để tránh việc phải tính toán lại một số kết quả trung gian nhiều lần, ta
cần xây dựng 1 bảng phương án lưu giữ các kết quả đã tìm được của các bài toán con.
Từ đó áp dụng cho việc tìm kiếm kết quả của các bài toán cha lớn hơn. Vậy quy hoạch
động bắt đầu từ việc giải tất cả các bài toán nhỏ nhất (bài toán cơ sở) để từ đó từng
bước giải quyết những bài toán lớn hơn cho tới khi giải được bài toán lớn nhất (bài

toán ban đầu).
Việc áp dụng bảng phương án đã khiến quy hoạch động tối uu hơn rất nhiều,
giảm thiểu các quá trình tính toán, và thể hiện sức mạnh của nguyên lý chia để trị đến
cao độ.
Quy hoạch động thường dùng một trong 2 cách tiếp cận sau:
• Tiếp cận từ dưới lên (bottom up)
• Tiếp cận từ trên xuống (top down)
Cách tiếp cận từ dưới lên hiệu quả hơn nên cách tiếp cận từ dưới lên (bottom up)
thường được sử dụng nhiều hơn.
Tóm lại:
• Quy hoạch động dùng để giải quyết bài toán tối ưu theo nguyên lý “chia để trị”
nhưng cải tiến hơn nhiều phương pháp giải quyết bài toán theo hướng đệ quy, và thể
hiện tư tưởng chia để trị đến cao độ.
• Quy hoạch động làm giảm độ phức tạp, giảm thời gian giải quyết bài
• Quy hoạch động thường tiếp cận theo hướng từ dưới lên (Bottom – up)
1.2.3 Phương pháp quy hoạch động
a. Bài toán thế nào thì được giải quyết bằng quy hoạch động?
Một bài toán tối ưu muốn giải được bằng phương pháp quy hoạch động khi bài
toán tối ưu đó có các đặc điểm dưới đây:
• Bài toán lớn phải phân rã được thành nhiều bài toán con, mà sự phối hợp lời giải
của các bài toán con đó cho ta lời giải của bài toán lớn.
• Vì quy hoạch động là đi giải tất cả các bài toán con nên nếu không đủ không
gian vật lý lưu trữ kết quả (bộ nhớ, đĩa …) để phối hợp chúng thì phương pháp
quy hoạch động cũng không thể thực hiện được.
• Quá trình từ bài bài toán cơ sở tìm ra lời giải bài toán ban đầu phải qua hữu hạn
bước.
b. Các bước thược hiện quy hoạch động
Bước 1: Lập hệ thức truy hồi
Dựa vào nguyên lý tối ưu, ta chia bài toán thành từng giai đoạn, tìm cách phân
rã bài toán thành các “bài toán con” tương tự có kích thước nhỏ hơn, tìm hệ thức quan

hệ giữa kết quả bài toán kích thước đã cho với kết quả của các “bài toán con” cùng
kiểu có kích thước nhỏ hơn của nó nhằm xây dựng hệ thức truy hồi.
Cụ thể, ta chia việc giải bài toán thành n trạng thái: n, n-1, n-2, …, 2, 1, 0. Mỗi
trạng thái k khi chịu tác động điều khiển d
k
sẽ biến thành trạng thái k+1. Trạng thái
k+1 khi chịu tác động điều khiển d
k+1
sẽ biến thành trạng thái k+2. Ta sẽ so sánh và tìm
điểm chung giữa các trạng thái d
0
, d
1
, d
2
, …, d
k
, d
k+1
, …, d
n-1
. Từ đó xây dựng hệ thức
truy hồi d là hệ thức truy hồi tổng quát cảu bài toán.
Bước 2: Tổ chức dữ liệu và chương trình
Ta tổ chức dữ liệu sao cho đạt các yêu cầu sau:
• Dữ liệu được tính toán dần theo các bước.
• Dữ liệu được lưu trữ vào bảng phương án để giảm lượng tính toán lặp lại.
• Kích thước bộ nhớ dành cho lưu trữ dữ liệu càng nhỏ càng tốt, kiểu dữ liệu được
chọn phù hợp, nên chọn đơn giản dễ truy cập.
Cụ thể:

• Các giá trị của F
k
thường được lưu trữ trong một bảng (mảng một chiều hoặc
hai, ba, v.v… chiều).
• Ta tính toán và lưu lại giá trị của các bài toán cơ sở (dễ dàng tìm ra cách giải) để
làm tiền đề giải quyết các bài toán lớn hơn.
• Dựa vào hệ thức truy hồi và kết quả của các bài toán cơ sở để giải các bài toán
lớn hơn
• Lưu trữ nghiệm tương ứng với các giá trị tối ưu trong từng gian đoạn vào bảng
một cách thích hợp
Bước 3: Truy vết, tìm nghiệm và làm tốt
Dựa vào bảng lưu trữ nghiệm và bảng phương án tối ưu trong từng giai đoạn đã
xây dựng, ta sẽ tìm ra kết quả nghiệm tối ưu của bài toán.
Sau khi tìm được nghiệm tối ưu của bài toán. Ta tìm cách làm tốt thuật toán
bằng cách thu gọn hệ thức truy hồi và giảm kích thước bộ nhớ. Ví dụ như tìm cách
giảm bộ nhớ lưu trữ từ mảng hai chiều thành mảng một chiều, từ mảng một chiều
xuống thành hệ số.
Trong thực tế, khi muốn làm tốt về không gian, ta sẽ phải tổ chức lại chương
trình và thực hiện thêm các bước làm tốt, dẫn tới thiệt hại về thời gian tính toán, và
ngược lại. Bởi vậy, trong một số trương hợp, ta cần điểu chỉnh giữa làm tốt không gian
và thời gian để đạt được hiệu suất àm việc tốt nhất.
1.2.4 Ưu điểm và hạn chế của phương pháp quy hoạch động
a. Ưu điểm
Phương pháp quy hoạch động cải thiện đáng kể cả về không gian lưu trữ và thời
gian tính toán so với hầu hết các phương pháp giải quyết bài toán tối ưu khác.
b. Hạn chế
Việc tìm hệ thức truy hồi hoặc tìm cách phân rã bài toán không phải là dễ.
Nhiều khi đòi hỏi sự phân tích tổng hợp rất công phu, dễ sai sót, khó nhận ra như thế
nào là thích hợp, đòi hỏi nhiều thời gian suy nghĩ. Đồng thời không phải lúc nào kết
hợp lời giải của các bài toán con cũng cho kết quả của bài toán lớn hơn.

Khi bảng lưu trữ đòi hỏi mảng hai, ba chiều … thì khó có thể xử lý dữ liệu khi
kích cỡ dữ liệu lớn. Có những bài toán tối ưu không thể giải được bằng quy hoạch
động
Tổng kết :
Không phải lúc nào việc kết hợp các bài toán con cũng cho ta kết quả của bài
toán lớn hơn. Hay nói cách khác là việc tìm kiếm "công thức truy hồi" rất khó khăn.
Ngoài ra, số lượng các bài toán con cần lưu trữ có thể rất lớn, không chấp nhận được vì
dữ liệu và bộ nhớ máy tính không cho phép.
CHƯƠNG 2: ỨNG DỤNG PHƯƠNG PHÁP QUY HOẠCH ĐỘNG GIẢI MỘT
SỐ BÀI TOÁN CỤ THỂ
1 Bài toán: Chia phần thưởng.
1.1.Phát biểu bài toán
Cần chia hết m phần thưởng cho n học sinh sắp theo thứ tự từ giỏi trở xuống sao
cho mỗi bạn không nhận ít phần thưởng hơn bạn xếp sau mình.
1 ≤ m, n ≤ 70.
1.2. Ý tưởng 30s
Liệt kê tất cả các cách chia thưởng. Thí dụ, với số phần thưởng m = 7, và số học
sinh n = 4 sẽ có 11 cách chia 7 phần thưởng cho 4 học sinh theo yêu cầu của đầu bài.
Đó là:
Phương án 1 2 3 4
1 7 0 0 0
2 6 1 0 0
3 5 2 0 0
4 5 1 1 0
5 4 3 0 0
6 4 2 1 0
7 3 3 1 0
8 3 2 2 0
9 4 1 1 1
10 3 2 1 1

11 2 2 2 1
1.3.Lập hệ thức
Gọi Chia(i, j) là số cách chia i phần thưởng cho j học sinh, ta thấy:
• Nếu không có học sinh nào (j = 0) thì không có cách chia nào (Chia = 0).
• Nếu không có phần thưởng nào (i = 0) thì chỉ có một cách chia (Chia = 1- mỗi
học sinh nhận 0 phần thưởng). Ta cũng quy ước Chia(0, 0) = 1.
• Nếu số phần thưởng ít hơn số học sinh (i < j) thì trong mọi phương án chia, từ
học sinh thứ i + 1 trở đi sẽ không được nhận phần thưởng nào:
Chia(i, j) = Chia(i, i) nếu i < j.
Ta xét tất cả các phương án chia trong trường hợp i ≥ j. Ta tách các phương án
chia thành hai nhóm không giao nhau dựa trên số phần thưởng mà học sinh đứng cuối
bảng thành tích, học sinh thứ j, được nhận:
• Nhóm thứ nhất gồm các phương án trong đó học sinh thứ j không được nhận
thưởng, tức là i phần thưởng chỉ chia cho (j – 1) học sinh và do đó, số cách chia,
tức là số phần tử của nhóm này sẽ là: Chia(i, j – 1).
• Nhóm thứ hai gồm các phương án trong đó học sinh thứ j cũng được nhận
thưởng. Khi đó, do học sinh đứng cuối bảng thành tích được nhận thưởng thì
mọi học sinh khác cũng sẽ có thưởng. Do ai cũng được thưởng nên ta bớt của
mỗi người một phần thưởng (để họ lĩnh sau), số phần thưởng còn lại (i − j) sẽ
được chia cho j học sinh. Số cách chia khi đó sẽ là Chia(i − j, j).
Tổng số cách chia cho trường hợp i ≥ j sẽ là tổng số phần tử của hai nhóm, ta có:
Chia(i, j) = Chia(i, j - 1) + Chia(i - j, j).
Tổng hợp lại ta có:
i: là số phần thưởng.
j: là số học sinh
Điều kiện Chia(i, j)
j = 0 Chia(i, j) = 0
i = 0 và j # 0 Chia(i, j) = 1
i < j Chia(i, i) =
i ≥ j Chia(i, j) = Chia(i, j - 1) + Chia(i - j, j)

1.4.Thuật toán
Ta có phương án đầu tiên của giải thuật Chia như sau:
Phương án 1: đệ quy, số cách chia i phần thưởng cho j học sinh.
function Chia(i,j: integer):longint;
begin
if j = 0 then Chia:=0
else {j > 0}
if i = 0 then {i = 0; j > 0}
Chia:=1
else {i,j > 0}
if i < j then {0 < i < j}
Chia:=Chia(i,i)
else {i >= j > 0}
Chia:=Chia(i,j-1)+Chia(i-j,j);
end;
Phương án này chạy chậm vì phát sinh ra quá nhiều lần gọi hàm trùng lặp. Bảng
dưới đây liệt kê số lần gọi hàm Chia khi giải bài toán chia thưởng với bảy phần thưởng
(m = 7) và 4 học sinh (n = 4). Thí dụ, hàm Chia(1,1) sẽ được gọi 9 lần,… Tổng số lần
gọi hàm Chia là 79. 79 lần gọi hàm để sinh ra kết quả 11 là quá tốn kém. Ta có phương
án đầu tiên của giải thuật Chia như sau:
0 1 2 3 4
0 0 9 1 1 0
1 9 9 2 1 0
2 6 6 1 0 0
3 5 5 2 1 1
4 3 3 1 1 0
5 2 2 1 0 0
6 1 1 0 0 0
7 1 1 1 1 1
Làm tốt lần 1: Phương án 1 khá dễ triển khai nhưng chương trình sẽ chạy rất lâu, bạn

hãy thử gọi Chia(66,32) để trải nghiệm được điều trên. Diễn tả đệ quy thường trong
sáng, nhàn tản, nhưng khi thực hiện sẽ sinh ra hiện tượng gọi lặp lại những hàm đệ
quy. Cải tiến đầu tiên là tránh những lần gọi lặp như vậy. Muốn thế chúng ta tính sẵn
các giá trị của hàm theo các trị của đầu vào khác nhau và điền vào một mảng hai chiều
cc. Mảng cc được mô tả như sau:
const
MN = 70;{gioi han tren cua m va n}
type
ml1 = array[0 MN ] of longint;
ml2 = array[0 mn ] of ml1;
var cc: ml2;
Ta quy ước cc[i, j] chứa số cách chia i phần thưởng cho j học sinh.
Theo phân tích của phương án 1, ta có:
• cc[0, 0] = 1; cc[i, 0] = 0, với i:=1 m.
• cc[i, j] = cc[i, i], nếu i < j
• cc[i, j] = cc[i, j - 1]+cc[i - j, j], nếu i ≥ j
Cột j -1 Cột j
… … … …
… … … … …
Dòng i - j … … [i – j, j]
… …
Dòng i [i, j – 1] [i, j]
… … … …
Từ đó ta suy ra quy trình điền trị vào bảng cc như sau:
Khởi trị
• cc[0, 0]:= 1;
• với i:=1 m: cc[i, 0]:= 0;
Điền bảng: Lần lượt điền theo từng cột j:= 1 n. Tại mỗi cột j ta đặt:
 với i:=0 j-1: cc[i,j ]:= cc[i, i];
 với i:= j m: cc[i, j]:= cc[i, j-1] + cc[i-j, j];

Nhận kết quả: Sau khi điền bảng, giá trị cc[m, n] chính là kết quả cần tìm.
Phương án 2: Dùng mảng 2 chiều.
cc[i,j] = số cách chia i phần thưởng cho j học sinh
function Chia2(m,n: integer):longint;
var i,j: integer;
begin
cc[0,0 ]:=1;
for i:=1 to m do cc[i,0]:=0;
for j:=1 to n do
begin
for i:=0 to j-1 do cc[i,j ]:=cc[i,i ];
for i:=j to m do cc[i,j ]:=cc[i,j-1 ]+cc[i-j,j ];
end;
Chia2:=cc[m,n];
end;
Làm tốt lần 2: Dùng mảng hai chiều chúng ta chỉ có thể tính toán được với dữ liệu nhỏ.
Bước cải tiến sau đây khá quan trọng: chúng ta dùng mảng một chiều. Quan sát kĩ quy
trình gán trị cho mảng hai chiều theo từng cột chúng ta dễ phát hiện ra rằng cột thứ j có
thể được tính toán từ cột thứ j - 1. Nếu gọi c là mảng một chiều sẽ dùng, ta cho số học
sinh tăng dần bằng cách lần lượt tính j bước, với j:= 1 n. Tại bước thứ j, c[i] chính là số
cách chia i phần thưởng cho j học sinh. Như vậy, tại bước thứ j ta có:
• c[i] tại bước j = c[i] tại bước (j – 1), nếu i < j. Từ đây suy ra đoạn c[0 (j – 1)]
được bảo lưu.
• c[i] tại bước j = c[i] tại bước (j – 1) + c[i – j] tại bước j, nếu i ≥ j.
Biểu thức thứ hai cho biết khi cập nhật mảng c từ bước thứ j – 1 qua bước thứ j ta phải
tính từ trên xuống, nghĩa là tính dần theo chiều tăng của i:= j m.
Mảng c được khởi trị ở bước j = 0 như sau:
• c[0] = 1; c[i] = 0, với i:= 1 m.
Với ý nghĩa là, nếu có 0 học sinh thì chia 0 phần thưởng cho 0 học sinh sẽ được
quy định là 1. Nếu số phần thưởng m khác 0 thì chia m phần thưởng cho 0 học sinh sẽ

được 0 phương án.
Ta có phương án 3, dùng một mảng một chiều c như sau:
Phương án 3: dùng mảng 1 chiều c.
Tại bước thứ j, c[i ] = số cách chia i phần thưởng cho j học sinh.
function Chia1(m,n: integer):longint;
var i,j: integer;
begin
fillchar(c,sizeof(c),0);
c[0 ]:=1;
for j:=1 to n do
for i:=j to m do c[i ]:=c[i ]+c[i-j ];
Chia1:=c[m ];
end
1.5.Tính đúng đắn
Trong bài toán này ta sử dụng tư tưởng quy hoạch động vào xây dựng công thức
truy hồi, và kiểm tra lại bằng đệ quy cùng một kết quả. Nên giải thuật chúng ta sử dụng
trên luôn cho kết quả đúng.
1.6.Độ phức tạp thuật toán
Đệ quy Quy hoạch động
Quy hoạch động
cải tiến
Không gian O(2
n
) O(m.n) O(m)
Thời gian O(2
n
) O(n
2
) O(n)
1.7.Ứng dụng thực tiễn :

• ân chia công việc trong thực tế.
• Lập lịch công việc, hoặc lập lịch cho máy tính…
• Liệt kê một dãy tăng dần, hoặc giảm dần.
1.8.Chương trình :
Code chương trình viết bằng C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeminarTin
{
class Program
{
static void Main()
{
Console.WriteLine("PHUONG PHAP QUY HOACH DONG:\n");
Console.WriteLine("Bai toan chia phan thuong\n");
Console.WriteLine("Nhap vao so hoc sinh:\n");
long n = long.Parse(Console.ReadLine());
Console.WriteLine("Nhap vao so phan thuong:\n");
long m = long.Parse(Console.ReadLine());
Console.WriteLine("So cach chia m phan thuong cho n hoc sinh:\n");
Console.WriteLine(Chia(m,n));
Console.ReadLine();
}
static long Chia(long m, long n)
{
long[] c = new long[m + 1];
for (int i = 0; i <= m; ++i) c[i] = 0;

c[0] = 1;
for (int j = 1; j <= n; ++j)
for (int i = j; i <= m; ++i)
c[i] += c[i - j];
return c[m];
}
}
}
Ảnh chạy chương trình:
2 Bài toán: Lập lịch thuê thợ may hàng tháng
2.1 Phát biểu bài toán.
Có một hợp đồng may của đối tác bên Mỹ kéo dài trong T tháng, người quản
lý cần phải lập lịch sử dụng thợ may mọi tháng cho dự án. Biết rằng:
 số thợ may tối thiểu cần trong tháng thứ i là Scn[i];
 tiền dịch vụ khi thuê 1 thợ may mới là DV;
 tiền đền bù khi sa thải một thợ may là ST;
 lương tháng mỗi thợ may phải trả là LT
Cần phải thuê hay sa thải bao nhiêu thợ may mỗi tháng để tổng chi phí thợ
may của dự án là nhỏ nhất.
Giả thiết Kết luận
T = 3
DV=4; ST= 5; LT=6 Scn={11;
9; 10}
265
11 10 10
2.2 Ý tưởng 30s.
Lúc bắt đầu sử dụng nhiều thợ may để hoàn thành lô hàng và điều chỉnh theo
từng tháng. Nhược điểm tốn thời gian, không phải lúc nào cũng tìm được số thợ may
cần thiết.
2.3 Lập hệ thức.

Tham số thể hiện kích thước bài toán là số tháng T
Tổng chi phí thợ may trong T tháng được tính từ tổng chi phí thợ may của
T-1 tháng cộng thêm chi phí trả thợ may của tháng thứ T.
Chi phí trả thợ may của tháng thứ T bao gồm :
 Tiền lương trả cho số thợ may của tháng T và
 Tiền dịch vụ nếu số thợ may của tháng T lớn hơn số thợ may tháng T-1 hay
tiền sa thải nếu số thợ may trong tháng T nhỏ hơn số thợ may của tháng T-1.
Kích thước bài toán phụ thuộc vào 2 tham số: số tháng và số thợ may của tháng
2.4 Thuật toán.
• Lập công thức đệ quy.
 Scn[i] luu số công nhân cần thuê cho tháng thứ i
 Smax là số công nhân của tháng cần nhiều nguời nhất
 Bài toán con nhỏ nhất ứng với i = 1 (tháng dầu tiên):
 C(1, j) = j * (DV + LT) với j = Scn[1] Smax
C(i, j) là chi phí tối thiểu của i tháng dầu tiên nếu tại tháng thứ i có j công nhân
duợc thuê.
C(i, j) = Min{ C(i-1, k) + chi phí dể từ k nguời thành j nguời }
( i=2 T; j =Scn[i] Smax; k = Scn[i-1] Smax)
 Kết quả bài toán là: Kq = Min{C(T, j) + chi phí sa thải j nguời}
j=Scn[T] Smax
Xây dựng bài toán chứa C(i,j)
 Mảng C[1 T+1, 1 Smax]: C[i, j] ghi nhận giá trị C(i, j)
C(1, j) = j * (DV + LT) với j = Scn[1] Smax
C(i, j) = Min{ C(i-1, k) + chi phí dể từ k nguời thành j nguời }
( i=1 T; j =Scn[i] Smax; k = Scn[i-1] Smax)
C(T+1, j) = C(T, j) + (j * ST) j=Scn[T] Smax
Scn i \ j 9
10 11
11 1
99

9 2
99+45+12=1
56
99+50+6=1
55
99+55=164
10 3 155+50=205 155+55+4=214
4 205+60=265 2144+66=280
 Mảng Truoc[1 T, 1 Smax]: Truoc[i, j] := k là số nguời
thuê ở tháng thứ i-1 dể có C[i, j]
Scn i \ j 9 10 11
11 1 99
9 2
99+45+12=15
6
99+50+6=155 99+55=164
10 3 155+50=205 155+55+4=214
4 205+60=265 2144+66=280
• Thuật toán tạo bảng phương án C và Truoc:
{
for (j=Scn[1]; j <= Smax; j++)
C[1, j] = j * (DV + LT);
for (i = 2; i <= T; i++)
for (j=Scn[i]; j <= Smax; j++)
{
C[i, j] = MAXINT;
}
}
for (k=Scn[i-1]; k <= Smax; k++)
{

X = C[i-1, k] ;
if (k > j) X = X + (k – j)*DV;
else X = X + (j – k)*ST
if (X< C[i, j])
{
C[i, j] = X; Truoc[i, j] = k; }
for (j=Scn[T]; j <= Smax; j++) C[T+1, j] = C(T, j) + (j * ST) ;
}
• Thuật toán truy vết số thợ may mỗi tháng
{
//Tìm số nhân công của tháng thứ T
S = C[T+1, Scn[T] ]; k = Scn[T];
for (j=Scn[T]+1; j <= Smax; j++)
}
if (S > C[T+1, j ])
{ k = j; S =C[T+1, j ]; }
<Tháng T cần k công nhân>
for (i=T; i > 1; i )
{ k = Truoc[i, k];
<Tháng i-1 cần k công nhân>
}
}
2.5 Tính đúng đắn.
Trong bài toán này ta sử dụng tư tưởng quy hoạch động vào xây dựng công
thức truy hồi, và kiểm tra lại bằng đệ quy cùng một kết quả. Nên giải thuật chúng ta
sử dụng trên luôn cho kết quả đúng.
2.6 Độ phức tạp thuật toán
Độ phức tạp Đệ quy Quy hoạch động
Quy hoạch động
cải tiến

Không gian O(2
n
) O(2n) O(n)
Thời gian O(2
n
) O(n
2
) O(n)
2.7 Ứng dụng
 Lập lịch cho công việc.
 Lập lịch cho trong máy tính.
3 Bài toán: Dãy con có tổng chia hết cho k
3.1 Phát biểu bài toán.
Cho dãy số gồm n số nguyên dương a
1
, a
2
, … , a
n
. Hãy đếm xem có bao
nhiêu dãy con ( các phần tử lien tiếp) có tổng chia hết cho k.
INPUT:
• Dòng 1: gồm 2 số nguyên dương n và k.
• Dòng 2: chứa n số nguyên dương.
OUTPUT: ghi kết quả tìm được.
3.2 Ý tưởng sau 30s
Sử dụng phương pháp vét cạn để giải bài toán. Liệt kê tất cả các phương án
có thể xảy ra. Chọn 2 điểm bất kì làm 2 đầu của dãy, vì có n số nên có n*(n-1)/2
phương án. Mỗi dãy con cần tính tổng của dãy xem có chia hết cho k hay không, độ
phức tạp là O(n) . Như vậy độ phức tạp của ý tưởng vét cạn là O(n

3
).
3.3 Phương pháp quy hoạch động
Cải tiến từ phương pháp vét: Ta thấy, muốn tính tổng 1 đoạn (i,j) bất kì ta có
thể tính toán đơn giản hơn bằng một phép tính B[j]- B[i-1] với mảng B ý nghĩa là B
i
INPUT OUTPUT
5 3
1 2 3 4 5
7

×