background image

1

Lời nói đầu

Lập trình động (còn gọi là phương pháp quy hoạch động) là một kĩ thuật rất

hiệu quả giải quyết nhiều bài toán tin học, đặc biệt là những bài toán tối ưu. Số

lượng  bài  toán được  giải  bằng  lập  trình  động  cũng  rất  lớn,  ví  dụ  riêng  kì  thi

Olympic quốc tế về Tin học IOI 2004 có tới 3 bài trong 6 bài thi có thể giải bằng

lập trình động. Nhiều năm gần đây, trong hầu hết các đề thi chọn HSG QG đều

có ít nhất 1 trong 3 bài có thể giải bằng phương pháp quy hoạch động.

Nhóm  tác  giả  chúng  tôi  biên  tập  tài  liệu “Bài  tập  quy  hoạch  động”  này

mong muốn giới thiệu lí thuyết và các bài tập từ đơn giản đến phức tạp của lập

trình động. Cuốn sách sẽ là tài liệu quí báu đối với học sinh năng khiếu Tin học,

sinh  viên  các  ngành  công  nghệ  thông  tin  và  giáo  viên  môn  Tin  học  của  các

trường THPT.

Tài liệu gồm 4 chương:

Chương I: Cơ sở lý thuyết

Chương II: Một số bài tập cơ bản

Chương III: Bài tập chọn lọc

Chương IV: Một số đề tự giải

Chương I nêu rõ tư tưởng, vị trí, ứng dụng của lập trình động và cách nhận

diện các bài tập có thể giải bằng phương pháp quy hoạch động. Chương II phân

tích và dẫn ra chương trình giải các bài toán kinh điển như: Tìm dãy con không

giảm dài nhất, Dãy con chung dài nhất, Tìm dãy con có tổng bằng S, ….Chương

III,  chương  IV  giới  thiệu  đề  bài,  cách  giải,  chương  trình  của  rất  nhiều  bài  tập

chọn lọc.

Chúng  tôi  chân  thành  cảm ơn  các  bạn  đồng  nghiệp đã  nhận  xét  và  góp ý

cho bản thảo, trân trọng cảm ơn BGH trường THPT Chuyên Bắc Giang đã khích

lệ, tạo điều kiện cho nhóm tác giả được nghiên cứu để tài liệu sớm được ra mắt

bạn đọc.

background image

2

Trong  quá  trình  biên  soạn,  mặc  dù  chúng  tôi đã  cố  gắng,  song  nội  dung

chuyên đề ngày càng có nhiều khía cạnh mới và sâu sắc nên chắc chắn tài liệu

còn nhiều hạn chế. Chúng tôi rất mong được bạn đọc xa gần góp ý, để lần tái bản

sau tài liệu sẽ hoàn thiện hơn.

Mọi góp ý, xin gửi đến địa chỉ:

Nhóm Tin học Trường THPT Chuyên Bắc Giang

background image

3

Chương I:

Cơ sở lý thuyết

I.1. Tư tưởng của phương pháp quy ho ạch động

I.1.1. Thuật toán chia để trị

Lập trình động cũng như chia để trị là các phương pháp giải một bài toán

bằng cách tổ hợp lời giải các bài toán con của nó.

Thuật toán chia để trị được coi là thuật toán thông dụng nhất trong Tin

học. Khi giải một bài toán P với kích thước ban đầu nào đó nếu gặp trở ngại vì

kích thước quá lớn, người ta thường nghĩ đến việc giải các bài toán tương tự

nhưng với kích thước nhỏ hơn (gọi là các bài toán con của P). Tư tưởng “chia để

trị” thường được nhắc tới như hình ảnh “bẻ dần từng chiếc đũa để bẻ gãy cả

bó đũa”.

Chia để trị thực hiện “tách” một bài toán ban đầu thành các bài toán con

độc lập, sau đó giải các các bài toán con này và 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.

Nhưng giải từng bài toán con như thế nào? Chúng ta có thể thực hiện một

cách đơn giản lại “tách” bài toán con thành các bài toán con nhỏ hơn nữa, và

cứ tiến hành như vậy cho đến khi gặp các bài toán con nhỏ đến mức dễ dàng

giải được. Các bài toán con cùng được sinh ra sau mỗi lần “tách” được gọi là

cùng mức. Những bài toán con sinh ra sau hơn thì ở mức dưới (thấp hơn).

Thủ tục đệ quy luôn là cách thường dùng và hiệu quả để thực hiện thuật

toán chia để trị. Quá trình đệ quy lần lượt xếp dần các bài toán con vào ngăn

xếp bộ nhớ và sẽ thực hiện giải các bài toán con theo thứ tự ngược lại từ bài

toán đơn giản nhất trên đỉnh ngăn xếp cho đến khi giải được bài toán ban đầu

ở đáy ngăn xếp.

Ví dụ: Cho mảng a[1..n] gồm các số đã sắp tăng. Tìm phần tử của mảng có

giá trị bằng số x cho trước.

background image

4

Xét chương trình thực hiện thuật toán tìm kiếm nhị phân (một kiểu chia

để trị rất phổ biến) để giải bài toán trên. Trong chương trình chính có lời gọi

thủ tục tim(1,n). Sau đây là thủ tục tim(i,j):

Procedure tim(i,j : integer); {Tim x c ó trong mảng a[i .. j] hay không}

Var k : integer;

Begin

K := (i+j) div 2;

If a[k] = x then

 Begin

Write(‘Co phan tu bang ‘,x);

Halt;

End

Else

    If (a[k] < x) and (k<j) then tim(k+1,j)

        Else

If (a[k] > x) and (k>i) then tim(i,k-1)

End;

Do chỉ tìm kiếm trên một trong hai phần được “tách” ra, nên độ phức tạp

thuật toán là O(log

2

n), tốt hơn là tìm kiếm tuần tự.

I.1.2. Hệ thức truy hồi

Giải bài toán thường tiến hành theo nhiều bước. Kết quả của một bước

thường  dựa  vào  kết  quả  của  các  bước  thực  hiện  trước đó.  Do  vậy,  cần  xây

dựng hệ thức thể hiện quan hệ về kết quả giữa các bước gọi là hệ thức truy

hồi.

Ví dụ 1: Cho dãy số Fibonaci {F

1

, F

2

, …, F

n

}, trong đó:

F

1

 = F

2

 = 1 và F

n

 = F

n-1

 + F

n-2

 với n > 2 (1). Tìm số hạng thứ n.

background image

5

Hệ  thức  tính  số  hạng  thứ  n  của  dãy  Fibonaci  là  hệ  thức (1) đã  cho  sẵn

trong đề bài.

Ví dụ 2: Cho một dãy N số nguyên A

1

, A

2

, …, A

n

. Hãy tìm cách xoá đi một số

ít nhất số hạng để dãy còn lại là đơn điệu hay nói cách khác hãy chọn một số

nhiều nhất các số hạng sao cho dãy B gồm các số hạng đó theo trình tự xuất

hiện trong dãy A là đơn điệu.

Minh hoạ cho ví dụ 2:

Dãy A: 1, 4, 10, 9, 8, 17, 11, 7, 12, 6

Dãy B: 1,4,10,11,12

Phân tích để giải bài toán ta thấy:

Khi dãy A có một phần tử a1thì dãy B có 1 phần tử.

Mở rộng bài toán với dãy A có 2 phần tử, có 3 phần tử, … để tìm độ dài

dãy B. Ta có bài toán tổng quát: Gọi F[i] là số phần tử của dãy con B khi dãy A

có các phần tử từ 1 đến i

Dễ dàng tính được:

F[i] := 1 nếu không tồn tại j:= 1..i-1 để A[j] < A[i]

F[i] := max{F[j], j=1..i-1 thoả mãn a[j] < a[i]} + 1; (2)

Như vậy, để giải bài toán ở ví dụ 2 ta phải tiến hành qua n bước. lời giải ở

bước  i  (i=1,2,…,n)  phụ  thuộc  vào  lời  giải  của  i-1  bước  trước đó.  Hệ  thức (2)

chính là hệ thức truy hồi của bài toán.

I.1.3. Lập trình động là gì?

Lập trình động giống phương pháp chia để trị ở chỗ: lời giải của bài toán

được tổ hợp từ lời giải các bài toán con.

“Chia để trị” sẽ phân chia bài toán ban đầu thành các bài toán con độc lập

(sự phân chia có cấu trúc dạng cây)giải các bài toán con này thường bằng đệ

quy, sau đó tổ hợp lời giải của chúng để được lời giải của bài toán ban đầu.

background image

6

Lập trình động (dynamic programming) cũng phân chia bài toán thành các

bài  toán  con,  nhưng các bài toán con phụ thuộc nhau (hay dùng từ gối nhau

cũng được), mỗi bài toán con có thể tham chiếu tới cùng một số bài toán con

mức dưới (sự phân chia không có cấu trúc dạng cây  xem hình 1).

Hình 1: Đồ thị bài toán con cho dãy Fibonacci. Đây không phải là một

cấu

trúc cây

 mà là một

đồ thị có hướng phi chu trình

 mô tả quan hệ giữa các bài

toán con gối nhau.

Nói rằng một bài toán có các bài toán con trùng nhau có nghĩa là mỗi bài

toán  con đó được sử dụng để giải nhiều bài toán lớn hơn khác nhau. Ví dụ,

trong

dãy Fibonacci

, F

3

 = F

1

 + F

2

 and F

4

 = F

2

 + F

3

— khi tính mỗi số đều phải tính

F

2

. Vì tính F

5

 cần đến cả F

3

 và F

4

, một cách tính F

5

 một cách ngây thơ có thể sẽ

phải tính F

2

 hai lần hoặc nhiều hơn. Điều này áp dụng mỗi khi có mặt các bài

toán con gối nhau: một cách tiếp cận ngây thơ có thể tốn thời gian tính toán lại

lời giải tối ưu cho các bài toán con mà nó đ ã giải.

Để tránh việc đó, ta lưu trữ lời giải của các bài toán con đã giải. Do vậy,

nếu sau này ta cần giải lại chính bài toán đó, ta có thể lấy và sử dụng kết quả

đã được  tính  toán.  Hướng  tiếp  cận  này  được  gọi  là lưu  trữ  (trong

tiếng  Anh

được gọi là

memoization

, không phải memorization, dù từ này cũng hợp nghĩa).

Nếu ta chắc chắn rằng một lời giải nào đó không còn cần thiết nữa, ta có thể

xóa nó đi để tiết kiệm không gian bộ nhớ. Trong một số trường hợp, ta còn có

thể tính lời giải cho các bài toán con mà ta biết trước rằng sẽ cần đến.

background image

7

Khi giải bài toán Fibonaci cỡ i (với i tăng từ 2 đến n), ta đều sử dụng kết

quả của 2 bài toán con cỡ i-1 và i-2;

Lập trình động do nhà toán học người Mỹ là Richard Bellman (1920-1984)

phát minh vào năm 1953.

Lập trình động thường dùng để giải các bài toán tối ưu – bài toán yêu cầu

tìm một giải pháp tốt nhất trong các giải pháp có thể tìm được.

Cơ sở của lập trình động trong bài toán tối ưu là nguyên lí tối ưu Bellman:

“Dãy tối ưu các quyết định trong một quá trình quyết định nhiều giai đoạn có

thuộc tính là dù trạng thái và các quyết định ban đầu bất kể thế nào, những

quyết định còn lại phải tạo thành một cách giải quyết tối ưu không phụ thuộc

vào trạng thái được sinh ra từ những quyết định ban đầu”

I.1.4. Phương pháp quy hoạch động

Trong  ngành  khoa  học  máy  tính, phương  pháp quy  hoạch  động  là  một

phương pháp giảm thời gian chạy của các thuật toán thể hiện các tính chất của

các bài toán con gối nhau và cấu trúc con tối ưu.

Tư tưởng chủ đạo của phương pháp quy hoạch động có thể tóm lược như

sau:

 Chia 1 bài toán lớn thành các bài toán con tương tự có kích thước nhỏ hơn

cho đến khi nhận được các bài toán con mà lời giải có thể xây dựng dễ dàng.

Ta phải tìm ra mối quan hệ giữa nghiệm của bài toán với nghiệm của các

bài toán con, mối quan hệ thể hiện 1 công thức truy hồi  hay một hàm gọi là

công thức quy hoạch động (hay hàm quy hoạch động).

Khi tính toán chúng ta xuất phát tính từ nghiệm các bài toán con ban đầu,

sau đó kết hợp nghiệm của các bài toán con theo công th ức quy hoạch động ta

được  nghiệm  bài  toán  con  có  cỡ  lớn  hơn.  Quá trình  cứ  tiếp  tục  như  vậy  cho

đến khi ta được nghiệm của bài toán đã cho.

background image

8

Vậy: Ý tưởng cơ bản của quy hoạch động thật đơn giản: tránh tính toán

lại mọi thứ hai lần, mà lưu giữ kết quả đã tìm kiếm được vào một bảng làm giả

thiết cho việc tìm kiếm những kết quả của trường hợp sau. Chúng ta sẽ làm

đầy dần giá trị của bảng này bởi các kết quả của những trường hợp trước đã

được giải. Kết quả cuối cùng chính là kết quả của bài toán cần giải

Ví dụ: Tính số hạng thứ n của dãy Fibonaci bằng phương pháp quy hoạch

động như sau:

Nghiệm của mỗi bài toán con chỉ cần tính 1 lần và được lưu trong mảng 1

chiều F. Khi tìm kết quả của các bài toán cỡ lớn hơn, kết quả của các bài toán

con chỉ việc lấy ra để sử dụng.

F1 := 1;   F2  := 1;

For i := 3 to n do F[i] := F[i-2] + F[i-1];

I.2. Các bước thực hiện giải bài toán bằng phương pháp quy

hoạch động

I.2.1. Các bước cơ bản:

Bước 1: Phân tích tìm công thức quy hoạch động

-  Tìm  ra  công  thức  nghiệm  của  bài  toán  lớn  thông  qua  việc  giải  các  bài

toán con.

- Xác định nghiệm của bài toán con đơn giản (trong trường hợp cơ sở)

Bước 2: Thực hiện tính toán và tổ chức dữ liệu

- Đầu tiên tính nghiệm của bài toán con đơn giản (cơ sở)

(Khởi tạo giá trị ban đầu cho hàm QHĐ)

-  Dựa vào hàm QHĐ đã xây dựng ta tính nghiệm của bài toán theo kích

thước lớn dần cho đến khi nhận được nghiệm của bài toán đã cho.

Bước 3: Tổ chức dữ liệu và cài đặt

background image

9

-  Thường  dùng  mảng  một  chiều  hoặc  hai  chiều  để  lưu  trữ  lại  các  giá  trị

của các bài toán con (giá trị của hàm QHĐ)

- Ngoài ra, chúng ta phải dùng một số biến, thông thường cũng là mảng để

lưu trữ lại trạng thái của bài toán để sau này chúng ta có thể truy xuất lời giải

của bài toán.

I.2.2. Tổ chức cài đặt:

- Có thể dùng vòng lặp

- Hoặc dùng đệ quy

- Nghiệm của các bài toán con được tính toán 1 lần và lưu trữ trong mảng

khi cần lấy ở mảng đó ra và sử dụng

I.2.3 Khi nào dùng phương pháp quy hoạch động?

- Khi gặp bài toán có tính chất truy hồi

-  Khi lời  giải  bài  toán ở  bước sau được  xây  dựng  thông  qua  lời  giải  bài

toán ở bước trước.

- Kích thước bài toán không quá lớn

*Ưu điểm:

- Lời giải ngắn gọn, sang sủa, có tính logic cao, chương trình chạy nhanh.

*Nhược điểm:

- Không phải bài nào cũng giải bằng QHĐ

- Việc tìm ra công thức QHĐ của nhiều bài toán là khó khăn

- Không có công thức chung

- Bị hạn chế về bộ nhớ vì chúng ta phải lưu trữ nghiệm của các bài toán

con.

background image

10

Chương II:

Một số bài tập cơ bản

Bài 1: Tìm dãy con không gi ảm nhiều phần tử nhất;

Cho dãy số nguyên có n phần tử a

1

, a

2

, …, a

n

. Một dãy con không giảm của

dãy đã cho là dãy các phần tử còn lại của dãy đó sau khi ta xóa bỏ một hoặc

một số phần tử bất kì của nó, các phần tử của dãy con tạo thành dãy không

giảm. Ví dụ: dãy 1,4,10,11,12 là một dãy con không giảm của dãy 1, 4, 10, 9, 8,

17, 11, 7, 12, 6.

Yêu cầu: Tìm dãy con không giảm của dãy a gồm nhiều phần tử nhất.

Dữ liệu: Vào từ file văn bản DAYCON.INP gồm:

Dòng đầu tiên ghi số N (0 < N < 10

5

).

Các dòng tiếp theo, mỗi dòng ghi 10 số nguyên của dãy N số nguyên a

1

, a

2

,

.., a

n

. Dòng cuối cùng có thể ít hơn 10 số. Các số trên một dòng cách nhau một

dấu cách.

Kết quả: Ghi ra file văn bản DAYCON.OUT gồm:

Số max là độ dài dãy con không giảm dài nhất tìm được.

Chỉ số xuất hiện của các số hạng của dãy con trong dãy đã cho.

Ví dụ:

DAYCON.INP

DAYCON.OUT

10

6  5  8  12   6   9   7  13   2  13

5

2   5    7   8   10

Hướng dẫn:

Gọi L(i) là độ dài dãy con tăng dài nhất, các phần tử lấy trong miền từ a

1

đến a

i

 và phần tử cuối cùng là a

i

.

Ta có công thức QHĐ để tính L(i) như sau:

L(1) = 1

L(i) = max(1, L(j)+1 với mọi phần tử j: 0 < j < i và a

j

≤ a

i

).

background image

11

Bảng phương án là một mảng một chiều L để lưu trữ các giá trị của hàm

QHĐ L(i). Đoạn chương trình tính các giá trị của mảng L như sau:

for i := 1 to n do

begin

L[i] := 1;

for j:=1 to i - 1 do

if (a[j]<=a[i]) and (L[i]<L[j]) then L[i]:=L[j]+1;

end;

Như vậy chi phí không gian của bài toán là O(n), chi phí th ời gian là O(n

2

).

* Chương trình cài đặt:

program   day_con_khong_giam_dai_nhat;

uses crt;

const   fi='DAYCON.inp';

        fo='DAYCON.out';

        max=100000;

type    arrA=array[0..max]of longint;

        arrB=array[1..max,1..max]of longint;

var     f:text;

        a:arrA;

        n,kq,luu,dem:longint;

        d,truoc,k:arrA;

procedure       init;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

background image

12

        readln(f,n);

        for i:=1 to n do

                read(f,a[i]);

        close(f);

end;

procedure       process;

var      i,j:longint;

begin

        for i:=1 to n do

                truoc[i]:=i;

        d[1]:=1;

        for i:=1 to n do

                begin

                        d[i]:=1;

                        for j:=1 to i-1 do

                                if a[i]>=a[j] then

                                        if d[j]+1>d[i]  then

                                                begin

                                                d[i]:=d[j]+1;

                                                truoc[i]:=j;

                                                end;

                end;

        kq:=0;

        for i:=1 to n do

                if d[i]>kq then begin kq:=d[i];luu:=i;end;

        dem:=0;

background image

13

        while truoc[luu]<>luu do

                begin

                        inc(dem);

                        k[dem]:=luu;

          luu:=truoc[luu];

                end;

        inc(dem);

        k[dem]:=luu;

end;

procedure       inkq;

var      i:longint;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,kq);

        for i:=dem downto 1 do

                write(f,k[i],' ');

        close(f);

end;

BEGIN

init;

process;

inkq;

END.

Bài 2: Dãy con chung dài nhất;

Cho hai dãy số nguyên a = (a

1

, a

2

, …, a

M

), b = (b

1

, b

2

, …, b

N

), với M,N < 200.

background image

14

Hãy tìm một dãy con chung c=(c

1

, c

2

, …, c

K

) của a và b gồm nhiều số hạng

nhất. Dãy con chung c nhận được bằng cách xóa đi một số số hạng của dãy a, c

cũng nhận được bằng cách xóa đi một số số hạng của dãy b, sau khi xóa ở hai

dãy giữ nguyên thứ tự các phần tử còn lại.

Dữ liệu: Vào từ file văn bản DAYCC.INP có cấu trúc như sau:

Dòng đầu tiên  ghi 2 số nguyên dương M, N.

Dòng thứ 2 ghi các số a

1

, a

2

, …, a

M

;

Dòng thứ 3 ghi các số b

1

, b

2

, …, b

N

;

Kết quả: Ghi ra file văn bản DAYCC.OUT có cấu trúc:

Dòng đầu ghi số K là số lượng các số hạng của dãy con chung c (nếu không

có dãy con chung thì ghi số 0);

Trong k dòng tiếp theo, dòng thứ i (I = 1, 2, .., k) ghi 2 số x, y với ý nghĩa là:

số hạng thứ i của dãy c là số hạng thứ x của dãy a và là số hạng thứ y của dãy

b;

Ví dụ:

DAYCC.INP

DAYCC.OUT

7 6

3 5 1 3 5 5 3

1 5 3 5 3 1

4

2 2

4 3

6 4

7 5

*Hướng dẫn:

Gọi  L(i,j)  là  độ  dài dãy  con  chung  dài  nhất  của dãy a(i)  gồm  i phần  tử

phần đầu của dãy a (a(i) = a[1..i]) và dãy b(j) gồm j phần tử phần đầu của dãy b

background image

15

(b(j) 

=b [1..j]).

Ta có công thức quy hoạch động như sau:

L(0,j)=L(i,0)=0.

L(i,j) = L(i - 1,j - 1)+1 nếu a[i] = b[j].

L(i,j) = max(L(i - 1,j), L(i,j - 1)) nếu a[i] ≠ b[j].

*Chương trình cài đặt:

Program Dayconchung;

uses crt;

const fi='daycc.inp';

       fo='daycc.out';

       maxmn=100;

var  f:array[0..maxmn,0..maxmn] of integer;

     g:text;

     m,n:integer;

     a,b:array[1..maxmn] of integer;

Procedure init;

var i:integer;

Begin

     assign(g,fi); reset(g);

     readln(g,m,n);

     for i:=1 to m do read(g,a[i]);

     for i:=1 to n do read(g,b[i]);

     close(g);

End;

Function max(x,y:integer):integer;

Begin

background image

16

     if x > y then max:=x else max:=y;

End;

Procedure Build;

var  i,j:integer;

Begin

     for i:= 0 to m do f[i,0]:=0;

      for i:= 0 to n do f[0,i]:=0;

     for i:=1 to m do

      for j:=1 to n do

       if a[i] <> b[j] then f[i,j] := max(f[i-1,j],f[i,j-1])

        else     f[i,j] := f[i-1,j-1] + 1;

End;

Procedure result;

var i,j:integer;

Begin

     assign(g,fo); rewrite(g);

     writeln(g,f[m,n]);

     i := m; j := n;

     while (i<>0) and (j<>0) do
     Begin
          if a[i] = b[j] then
          Begin
               writeln(g,i,' ',j);
               dec(i);
               dec(j);
          End
          else

background image

17

          Begin
               if f[i,j] = f[i-1,j] then  dec(i)
               else   if f[i,j] = f[i,j-1] then  dec(j);
          end;

     end;

     close(g);

End;

BEGIN

     init;

     build;

     result;

END.

Bài 3: Dãy con có tổng bằng S

Cho dãy a

1

, a

2

,.. a

n

. Tìm một dãy con của dãy đó có tổng bằng S.

Dữ liệu: vào từ  file văn bản "tongs.inp" có dạng :

- Dòng đầu tiên là 2  số N, S (N<200);

- Dòng thứ hai là N số A

i

 (i=1, 2,.., N; -32000 <= A

i

 <=32000).

Kết quả: Ghi ra file văn bản "tongs.out" có dạng:

- Các phần tử thuộc dãy con tìm được

Ví dụ:

tongs.inp

Tongs.out

8  39

19  5  20  13  16  20  18  2

19  20

* Hướng dẫn:

background image

18

Đặt  L(i,t)=1  nếu có  thể  tạo  ra  tổng  t  từ  một  dãy  con  của  dãy  gồm  các

phần tử a

1

,a

2

,..a

i

.  Ngược  lại  thì  L(i,t)=0.  Nếu  L(n,S)=1  thì đáp  án  của  bài  toán

trên 

là 

'có'.

Ta  có  thể  tính  L(i,t)  theo  công  thức:  L(i,t)  =  1  nếu  L(i -  1,t)=1  hoặc  L(i-1,t -

a[i])=1.

* Chương trình cài đặt:

Program  day_con_tong_s;

uses crt;

const   fi='tongs.inp';

        fo='tongs.out';

        max=6400;

        maxn=200;

type    arrA=array[1..maxn]of longint;

        arrB=array[0..max]of longint;

var     f:text;

        n:longint;

        a,truoc:arrA;

        s:longint;

        d:arrB;

procedure       init;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n,s);

        for i:=1 to n do

background image

19

                read(f,a[i]);

        close(f);

end;

procedure       process;

var     i,j:longint;

begin

        for i:=1 to s do  d[i]:=0;

        for i:=1 to n do

                for j:=s downto a[i] do

                        begin

                                if (d[j]=0) and (d[j -a[i]]=1) then

                           begin

                                                d[j]:=1;

                                                truoc[j]:=i;

                                        end;

                                if d[s]<>0 then exit;

                  end;

end;

procedure inkq;

var    i:longint ;

begin

        assign(f,fo);rewrite(f);

        if d[s]=0 then write(f,'0')

        else

        begin

                i:=truoc[s];

background image

20

                while s<>a[i] do

                        begin

                                write(f,a[i],' ');

                                s:=s-a[i];

                                i:=truoc[s];

                        end;

                write(f,a[i]);

        end;

        close(f);

end;

BEGIN

        init;

        process;

        inkq;

END.

Bài 4: Xếp đồ vật vào ba lô (mỗi đồ vật chỉ có 1)

Một chiếc ba lô có thể chứa được một khối lượng không quá W. Có N đồ

vật được đánh số từ 1 đến N, đồ vật i có khối lượng A

i

 và giá trị sử dụng C

i

 (i=1,

2, …,N). Hãy chọn các đồ vật để xếp vào ba lô sao cho tổng giá trị các đồ vật

trong ba lô là lớn nhất (nếu có thể xếp được).

Dữ liệu: vào từ file văn bản BALO.INP có cấu trúc như sau:

Dòng đầu ghi 2 số nguyên dương N, W (0 < N,W < 100).

Dòng thứ i+1, với i=1,2,..,N, ghi 2 số nguyên dương A

i

 và C

i

 tương ứng là

khối lượng và giá trị sử dụng của đồ vật thứ I (0 < A

i

, C

i

< 256).

Kết quả: Ghi ra file văn bản BALO.OUT có cấu trúc:

Dòng đầu ghi tổng giá trị sử dụng lớn nhất tìm được.

background image

21

Dòng 2 ghi chỉ số các đồ vật được xếp vào ba lô.

Ví dụ:

BALO.INP

BALO.OUT

7  20

3  9

8  23

23  4

29  34

12  4

1  2

8  6

40

1  2  6  7

*Hướng dẫn

Gọi L(i,t) là tổng giá trị lớn nhất khi được chọn i vật từ 1 đến i cho vào

balô với tổng khối lượng không vượt quá t.

L(n,m) sẽ là đáp số của bài toán (là giá trị lớn nhất có được nếu chọn n vật

và tổng khối lượng không vượt m).

Công thức tính L(i,t) như sau:

L(i,0)=0; L(0,t)=0.

L(i,t)=L(i–1,t) nếu t<A

i

.

L(i,t)=max(L(i–1,t), L(i–1,t–a

i

)+b

i

) nếu t ≥ a

i

. Trong đó: L(i–1,t) là giá trị có

được nếu không đưa vật i vào balô, L(i–1,t–a

i

)+b

i

 là giá trị có được nếu chọn vật

i.

Ta có thể dùng một mảng 2 chiều để lưu bảng phương án, tuy nhiên dựa

trên nhận xét rằng để tính dòng i của bảng phương án chỉ cần dòng i–1, ta chỉ

cần dùng 2 mảng một chiều P và L có chỉ số từ 0 đến m để lưu 2 dòng đó. Đoạn

chương trình con tính bảng phương án như sau.

background image

22

L[t] := 0; {với mọi t}

for I := 1 to n do begin

P:=L;

for t := 0 to m do

if t< L[t]:="P[t]" then> else L[t] := max(P[t],P[t –a[i]]);

end;

Nếu  để  ý  kĩ  bạn  sẽ  thấy  rằng  đoạn  trình  trên  chỉ  viết  giống  công  thức

QHĐ  chứ  chưa  tối  ưu.  Chẳng  hạn  đã  có  lệnh  gán  P:=L,  sau  đó  lại  có  gán

L[t]:=P[t] với các giá trị t < a[i] là không cần thiết. Bạn đọc có thể tự cải tiến để

chương trình tối ưu hơn. Chi phí không gian của cách cài đặt trên là O(m) và chi

phí thời gian là O(n.m).

*Chương trình cài đặt:

program         xep_do_vat;

uses crt;

const   fi='balo.inp';

        fo='balo.out';

        max=10000;

type    arrA=array[1..max]of longint;

        arrB=array[0..max]of longint;

var     f:text;

        n,w,kq,luu:longint;

        a,c :arrA;

        d,gt:arrB;

        truoc:arrA;

procedure       init;

var     i:longint;

background image

23

begin

        assign(f,fi);

        reset(f);

        readln(f,n,w);

        for i:=1 to n do

                readln(f,a[i],c[i]);

        for i:=1 to w do

     begin

                        d[i]:=0;

                        truoc[i]:=0;

                        gt[i]:=0;

                end;

        close(f);

        kq:=0;

end;

procedure       process;

var     i,j:longint;

begin

        d[0]:=1;  gt[0]:=0;

      for i:=1 to n do

                begin

                        for j:=w downto a[i] do

                                if  (d[j-a[i]]=1) then

                                        if gt[j-a[i]]+c[i]>gt[j] then

begin

                                                 d[j]:=1;

background image

24

 gt[j]:=gt[j-a[i]]+c[i];

                                                 truoc[j]:=i;

                                                 if gt[j]>kq then

begin

kq:=gt[j];

luu:=j;

end;

end;

                        write(i,' ',truoc[87]);

      readln;

                end;

end;

procedure       inkq;

var    i :longint;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,kq);

        i:=truoc[luu];

                while luu>a[i] do

                        begin

               write(f,i,' ');

                                luu:=luu-a[i];

                                i:=truoc[luu];

                        end;

               write(f,i);

background image

25

        close(f);

end;

BEGIN

init;

process;

inkq;

END.

background image

26

Chương III:

Bài tập chọn lọc

Bài 5: Bố trí phòng họp;

 Có n cuộc họp đánh số từ 1 đến n đăng ký làm việc tại một phòng hội

thảo. Cuộc họp i cần được bắt đầu tại thời điểm s

i

 và kết thúc tại thời điểm f

i

 .

Hỏi có thể bố trí phòng hội thảo phục vụ được nhiều nhất bao nhiêu cuộc họp

sao cho khoảng thời gian làm việc của hai cuộc họp được nhận phục vụ chỉ có

thể giao nhau tại đầu mút.

Dữ liệu: vào từ file văn bản ACTIVITY.INP

Dòng dầu tiên chứa số nguyên dương n (n <= 1000000)

Dòng thứ i trong số n dòng tiếp theo chứa hai số nguyên dương s

i

, f

i

 (s

i

, f

i

<= 32000), i=1,2, …,n

Kết quả: Ghi ra file văn bản ACTIVITY.OUT

Dòng đầu tiên ghi số k là số lượng cuộc họp được chấp nhận phục vụ;

Mỗi dòng trong K dòng tiếp theo ghi chỉ số của một trong k cuộc họp được

chấp nhận

Ví dụ:

ACTIVITY.INP

ACTIVITY.OUT

5

1 3

2 4

1 6

3 5

7 9

3

1

4

5

background image

27

* Hướng dẫn:

Sắp xếp các cuộc họp tăng dần theo thời điểm kết thúc (b

i

). Thế thì cuộc

họp i sẽ bố trí được sau cuộc họp j nếu và chỉ nếu j < i va` b

j

≤ a

i

. Yêu cầu bố trí

được  nhiều  cuộc  họp  nhất  có  thể đưa  về  việc  tìm  dãy  các  cuộc  họp  dài  nhất

thoả mãn điều kiện trên

* Chương trình cài đặt:

program      bo_tri_phong_hop;

uses crt;

const   fi='activity.inp';

fo='activity.out';

max=1000000;

type    arrA=array[1..max]of longint;

arrB=array[1..max,1..max]of longint;

var     f:text;

        n,luu,kq,dem:longint;

        a,b,truoc,cs:arrA;

        d,k:arrA;

procedure       init;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                begin

                readln(f,a[i],b[i]);

background image

28

                cs[i]:=i;

                end;

close(f);

end;

procedure       sap_xep;

var     i,j,tg:longint;ok:boolean;

begin

        for i:=1 to n-1 do

                begin

                        ok:=true;

                        for j:=n downto i+1 do

                                if b[j]<b[j-1] then

                                begin

                                  tg:=b[j];b[j]:=b[j -1];b[j-1]:=tg;

                  tg:=a[j];a[j]:=a[j-1];a[j-1]:=tg;

                                  tg:=cs[j];cs[j]:=cs[j -1];cs[j-1]:=tg;

                                  ok:=false;

                                end;

                        if ok then exit;

       end;

end;

procedure       process;

var   i,j:longint;

begin

        d[1]:=1;

        for i:=1 to n do

background image

29

        truoc[i]:=i;

        for i:=2 to n do

                begin

                        d[i]:=1;

                        for j:=1 to n-1 do

                             if b[j]<=a[i] then

                                        if d[j]+1>d[i] then

                                                begin

                                                        d[i]:=d[j]+1;

                                truoc[i]:=j;

                                                end;

                end;

        kq:=-1;

        for i:=1 to n do

                if d[i]>kq then begin kq:=d[i];luu:=i;end;

        while luu<>truoc[luu] do

            begin

                        inc(dem);

                        k[dem]:=luu;

                        luu:=truoc[luu];

                end;

        inc(dem);

        k[dem]:=luu;

end;

procedure       inkq;

var  i:longint;

background image

30

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,kq);

        for i:=dem downto 1 do

                writeln(f,cs[k[i]]);

        close(f);

end;

BEGIN

init;

sap_xep;

process;

inkq;

END.

Bài 6: Cho thuê máy tính;

Tại thời điểm 0, ông chủ cho thuê máy tính nhận được đơn đặt hàng thuê

sử dụng của N khách hàng. Các khách hàng đư ợc đánh số từ 1 đến N. Khách

hàng i cần sử dụng máy từ thời điểm di đến thời điểm ci (di và ci là các số

nguyên  và  0  <  di  <  ci  <  1000000000),  và  s ẽ  trả  tiền  sử  dụng  máy  là  pi  (pi

nguyên, 0 < pi < 10000000);

Yêu cầu: Hãy xác định xem ông chủ cần nhận phục vụ những khách hàng

nào sao cho khoảng thời gian sử dụng máy tính của hai khách hàng được nhận

phục vụ bất kì không giao nhau và tổng tiền thu được từ phục vụ là lớn nhất.

Dữ liệu: vào từ file văn bản RENTING.INP. Dòng đầu tiên ghi số N (0 < N <

1000). Dòng thứ i trong N dòng tiếp theo ghi ba số di, ci, pi cách nhau bởi dấu

trống, i=1,2,..,N.

background image

31

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

150   500  150

1       200   100

400   800    80

2    180

2    3

4

400  821  800

200 513   500

100  325   200

600   900  600

2    1100

2    4

*Hướng dẫn:

Tương  tự  như  bài  toán  2,  nếu sắp  xếp  các  đơn  đặt  hàng  theo  thời  điểm

kết thúc, ta sẽ đưa được bài toán 3 này về bài toán tìm dãy con có tổng lớn

nhất.

* Chương trình cài đặt:

program   cho_thue_may_tinh;

uses crt;

const   fi='renting.inp';

        fo='renting.out';

        max=1000;

type    arrA=array[1..max]of int64;

        arrB=array[1..max,1..max]of longint;

var     f:text;

        a,b,c:arrA;

        n,kq,luu,dem:longint;

background image

32

d,cs,truoc,k:arrA;

procedure       init;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                begin

                readln(f,a[i],b[i],c[i]);

                cs[i]:=i;

  end;

        close(f);

end;

procedure       sap_xep;

var     i,j,tg:longint;ok:boolean;

begin

        for i:=1 to n-1 do

                begin

                        ok:=true;

                        for j:=n downto i+1 do

  if b[j]<b[j-1] then

                                begin

                                     tg:=b[j];b[j]:=b[j -1];b[j-1]:=tg;

                                    tg:=a[j];a[j]:=a[j -1];a[j-1]:=tg;

                                    tg:=cs[j];cs[j]:=cs [j-1];cs[j-1]:=tg;

background image

33

                                    tg:=c[j];c[j]:=c[j -1];c[j-1]:=tg;

                                    ok:=false;

                                end;

                        if ok then exit;

                end;

end;

procedure

process;

var   i,j:longint;

begin

        d[1]:=c[1];

        for i:=1 to n do

        truoc[i]:=i;

        for i:=2 to n do

                begin

                        d[i]:=c[i];

                        for j:=1 to n-1 do

  if b[j]<=a[i] then

                                        if d[j]+c[i]>d[i] then

                                                begin

                                                        d[i]:=d[j]+c[i];

           truoc[i]:=j;

                                                end;

                end;

        kq:=-1;

        for i:=1 to n do

                if d[i]>kq then begin kq:=d[i];luu:=i;end;

background image

34

        while luu<>truoc[luu] do

                begin

                     inc(dem);

                        k[dem]:=luu;

                        luu:=truoc[luu];

                end;

        inc(dem);

        k[dem]:=luu;

end;

procedure       inkq;

var  i:longint;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,dem,' ',kq);

        for i:=dem downto 1 do

                write(f,cs[k[i]],' ');

        close(f);

end;

BEGIN

init;   sap_xep;

process;

inkq;

END.

Bài 7: Nối điểm

Trên  hai  đường  thẳng  song  song  L1  và  L2  ngư ời  ta  đánh  dấu  trên  mỗi

đường N điểm. Các điểm trên đường thẳng L1 được đánh số từ 1 đến N từ trái

background image

35

sang phải, còn các điểm trên đường thẳng l2 được đánh số p1, p2, …, pN cũng

từ trái qua phải với p1, p2, …, pN là một hoán vị của 1, 2, …, N (Hình vẽ dưới

đây cho một ví dụ khi N=9):

Ta  gọi  các  số  gán  cho  các điểm  là  số  hiệu  của  chúng.  Cho  phép  nối  hai

điểm trên hai đường thẳng có cùng số hiệu.

Yêu cầu tìm cách nối được nhiều cặp điểm nhất với điều kiện các đoạn

nối không được cắt nhau.

Dữ liệu: vào từ file văn bản WIRE.INP có cấu trúc như sau:

Dòng đầu tiên chứa số nguyên dương N (N < 1000);

Dòng thứ hai chứa các số nguyên p1, p2, …, pN cách nhau b ởi dấu trắng;

Kết quả: Ghi ra file văn bản WIRE.OUT có cấu trúc:

Dòng đầu tiên chứa số k là số lượng các đoạn nối tìm được;

Dòng tiếp theo chứa k số hiệu của các đầu mút của các đoạn nối được ghi

theo thứ tự tăng dần.

Ví dụ:

WIRE.INP

WIRE.OUT

9

2  5  3  8  7  4  6  9  1

5

 3  4  6  9

*Hướng dẫn:

Đây chính là bài toán 1: Tìm dãy con không gi ảm dài nhất

* Chương trình cài đặt:

program   day_con;

uses crt;

background image

36

const   fi='wires.inp';

        fo='wires.out';

        max=1000;

type    arrA=array[0..max]of longint;

        arrB=array[1..max,1..max]of longint;

var     f:text;

        a:arrA;

        n,kq,luu,dem:longint;

        d,truoc,k:arrA;

procedure       init;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                read(f,a[i]);

        close(f);

end;

procedure       process;

var      i,j:longint;

begin

        for i:=1 to n do

                truoc[i]:=i;

        d[1]:=1;

        for i:=1 to n do

background image

37

                begin

                        d[i]:=1;

                        for j:=1 to i-1 do

                          if a[i]>=a[j] then

                                        if d[j]+1>d[i] then

                                                begin

                                                d[i]:=d[j]+1;

             truoc[i]:=j;

                                                end;

                end;

        kq:=0;

        for i:=1 to n do

                if d[i]>kq then begin kq:=d[i];luu:=i;end;

        dem:=0;

        while truoc[luu]<>luu do

        begin

                        inc(dem);

                        k[dem]:=luu;

                        luu:=truoc[luu];

                end;

        inc(dem);

        k[dem]:=luu;

end;

procedure       inkq;

var      i:longint;

begin

background image

38

        assign(f,fo);

        rewrite(f);

        writeln(f,kq);

        for i:=dem downto 1 do

                write(f,a[k[i]],' ');

        close(f);

end;

BEGIN

init;

process; inkq;

END.

Bài 8: Dãy con đổi chiều, đối dấu dài nhất;

Cho dãy a

1

, a

2

,... a

n

. Hãy tìm dãy con đổi chiều dài nhất của dãy đó. Dãy

con con đổi dấu a

i1

,a

i2

,... a

ik

 phải thoả mãn các điều kiện sau:

a

i1

 < a

i2

> a

i3

 <... hoặc a

i1

 > a

i2

 < a

i3

 >... các chỉ số phải cách nhau ít nhất L: i

2

- i

1

≥ L, i

3

-i

2

≥ L... chênh lệch giữa 2 phần tử liên tiếp nhỏ hơn U: |a

i1

- a

i2

| ≤ U,

|a

i2

- a

i3

| ≤ U...

Ví dụ: Cho dãy 10 phần tử: -1, 3, -4, 13, 6, 9, -2, 12, -3, 15  và L=2 và U=3

Có 1 dãy con đổi chiều dài nhất là: -1, -4, -2, -3

Dữ liệu: Vào từ file văn bản DOICHIEU.INP có cấu trúc như sau:

Dòng đầu  tiên  ghi  3  số nguyên dương  n,  L,  U  (n <10

5

;  1<L<n-1;  0 <  U <

1000);

Các dòng tiếp theo ghi n số của dãy a; (|a

i

| <= 32000);

Kết quả: Ghi ra file văn bản DOICHIEU.OUT có cấu trúc như sau:

Dòng đầu là độ dài dãy con đổi chiều dài nhất tìm được;

Chỉ số các phần tử của dãy con tìm được trong dãy ban đầu;

background image

39

Ví dụ:

DOICHIEU.INP

DOICHIEU.OUT

10  2  3

-1  3 -4  13  6  9 -2  12 -3 15

4

1  3  7  9

* Hướng dẫn:

Gọi L(i) là số phần tử của dãy con đổi dấu có phần tử cuối cùng là a

i

 và

phần tử cuối cùng lớn hơn phần tử đứng trước. Tương tự, P(i) là số phần tử

của dãy con đổi dấu có phần tử cuối cùng là a

i

 và phần tử cuối cùng nhỏ hơn

phần tử đứng trước. Ta dễ dàng suy ra:

L(i) = max(1, P(j)+1): j ≤ i - L và a

i

- U ≤ a

j

 < a

i

.

P(i) = max(1, L(j)+1): j ≤ i - L và a

i

 < a

j

≤ a

i

 + U.

* Chương trình cài đặt:

program     day_con_doi_chieu_dai_nhat;

uses crt;

const   fi='doichieu.inp';

        fo='doichieu.out';

        max=100000;

type    arrA=array[1..max]of longint;

        arrB=array[1..max,1..max]of longint;

var     f:text;

        n,l,u,luu,kq,cs,dem:longint;

        a:arrA;

        d1,d2,truoc1,truoc2,k:arrA;

procedure       init;

var            i:longint;

begin

background image

40

        assign(f,fi);

        reset(f);

       readln(f,n,l,u);

        for i:=1 to n do

                begin

                read(f,a[i]);

                truoc1[i]:=i;

                truoc2[i]:=i;

                end;

        close(f);

end;

procedure       process;

var     i,j:longint;ok:boolean;

begin

        d1[1]:=1;

        d2[1]:=1;

        for i:=2 to n do

                begin

                        d1[i]:=1;

                        for j:=1 to i-l do

                                if a[i]>=a[j] then

       if abs(a[i]-a[j])<=U then

                                                if d2[j]+1>d1[i] then

                                                 begin

d1[i]:=d2[j]+1;

 truoc1[i]:=j;

background image

41

                                                 end;

           d2[i]:=1;

                        for j:=1 to i-l do

                                if a[i]<=a[j] then

                                        if abs(a[i]-a[j])<=U then

                                                if d1[j]+1>d2[i] then

                                            begin

 d2[i]:=d1[j]+1;

                                                    truoc2[i]:=j;

                                                 end;

                end;

        kq:=-1;

        for i:=1 to n do

                begin

                        if d1[i]>kq then begin kq:=d1[i];cs:=1;luu:=i;end;

                        if d2[i]>kq then begin kq:=d2[i];cs:=2;luu:=i;end;

                end;

end;

procedure

  truy_xuat;

var ok:boolean;

begin

        ok:=false;

        dem:=0;

        repeat

                inc(dem);

                k[dem]:=luu;

background image

42

                if cs=1 then

                        begin

                                if luu=truoc1[luu]then

                                      exit

                                else

                                        begin

                                        luu:=truoc1[luu];

                                        cs:=2;

               end;

                        end

                else

                        begin

                                if luu=truoc2[luu] then

                                        exit

                                else

                     begin

                                        luu:=truoc2[luu];

                                        cs:=1;

                                        end;

                        end;

        until ok;

end;

procedure       inkq;

var    i:longint;

begin

        assign(f,fo);

background image

43

        rewrite(f);

        writeln(f,kq);

        for i:=dem downto 1 do

                write(f,k[i],' ');

        close(f);

end;

BEGIN

init; process;

truy_xuat; inkq;

END.

Bài 9: Số phép biến đổi ít nhất

Cho 2 xâu X,Y. Có 3 phép biến đổi với xâu X: chèn 1 kí tự, thay thế một kí

tự  hoặc  xoá  một  kí  tự.  Hãy  tìm  số  ít  nhất  các  phép  biến  đổi  để  biến  xâu  X

thành 

xâu 

Y.

Ví dụ: Cho xâu X=’kitten’ và  Y=’sitting’ thì c ần ít nhất là 3 phép biến đổi

Dữ liệu: Vào từ file văn bản BIENDOI.INP có cấu trúc như sau:

Dòng 1 chứa xâu X

Dòng 2 chứa xâu Y

(Mỗi xâu có không quá 200 kí tự)

Kết quả: Ghi ra file văn bản BIENDOI.OUT ghi 1 số là số phép biến đổi ít

nhất tìm được;

Ví dụ:

BIENDOI.INP

BIENDOI.OUT

kitten

sitting

3

* Hướng dẫn:

background image

44

Gọi F(i,j) là số phép biến đổi ít nhất để biến xâu X(i) gồm i kí tự phần đầu

của X (X(i)= X[1..i]) thành xâu Y(j) g ồm j kí tự phần đầu của Y (Y(j) =Y[1..j]). Dễ

thấy F(0,j)=j và F(i,0)=i.

Nếu X[i]=Y[j] thì ta chỉ phải biến đổi xâu X(i-1) thành xâu Y(j-1). Do đó

F(i,j)=F(i-1,j-1).

Ngược lại, ta có 3 cách biến đổi:

- Xoá kí tự X[i] và biến đổi xâu X(i-1) thành Y(j). Khi đó F(i,j)=F(i-1,j)+1.

- Thay thế X[i] bởi Y[j] và biến đổi X(i-1) thành Y(j-1). Khi đó F(i,j)=F(i-

1,j-1)+1.

- Chèn Y[j] vào X(i) và biến đổi X(i) thành Y(j-1). Khi đó F(i,j)=F(i,j-1)+1.

Tổng kết lại, ta có công thức QHĐ:

F(0,j)=j

F(i,0)=i

F(i,j) =F(i - 1,j - 1) nếu X[i] = Y[j].

F(i,j) = min(F(i - 1,j),F(i,j - 1),F(i - 1,j - 1))+1 nếu X[i] ≠ Y[j].

*Chương trình cài đặt:

program bien_doi_xau;

uses crt;

const   fi='biendoi.inp';

        fo='biendoi.out';

        max=100;

type    arrA=array[1..max]of char;

        arrB=array[0..max,0..max]of longint;

var     f:text;

        m,n:longint;

        d:arrB;

background image

45

        a,b:arrA;

procedure       init;

begin

        assign(f,fi);

        reset(f);

        m:=0;

        while not eoln(f) do

        begin

        inc(m);read(f,a[m]);

        end;

        readln(f);

        n:=0;

        while not eof(f) do

    begin

        inc(n);read(f,b[n]);

        end;

        close(f);

end;

function min3so(x,y,z:longint):longint;

var tam:longint;

begin

        if x<y then tam:=x

        else tam:=y;

        if z<tam then tam:=z;

        min3so:=tam;

end;

background image

46

procedure

  process;

var     i,j:longint;

begin

        for i:=1 to m do d[i,0]:=i;

        for j:=1 to n do d[0,j]:=j;

        for i:=1 to m do

          for j:=1 to n do

                   begin

                    if a[i]=b[j] then d[i,j]:=d[i -1,j-1]

         else

                        d[i,j]:=min3so(d[i-1,j-1],d[i,j-1],d[i-1,j])+1;

                   end;

end;

procedure       inkq;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,d[m,n]);

        close(f);

end;

BEGIN

init;

process;

inkq;

END.

background image

47

Bài  10: Xâu đối xứng;

Một xâu gọi là xâu đối xứng (palindrom) nếu xâu đó đọc từ trái sang phải

hay từ phải sang trái đều như nhau. Cho một xâu S, hãy tìm số kí tự ít nhất cần

thêm vào S để S trở thành xâu đối xứng.

Ví dụ: Cho xâu S=’abcda’ thì số kí tự ít nhất cần chèn thêm là 2

Dữ liệu: Vào từ file văn bản XDX.INP là 1 xâu S có không quá 500 kí t ự)

Kết quả: Ghi ra file văn bản XDX.OUT là số lượng kí tự ít nhất cần thêm;

Ví dụ:

XDX.INP

XDX.OUT

abcda

2

*Hướng dẫn:

Gọi L(i,j) là số kí tự ít nhất cần thêm vào xâu con S[i..j] của S để xâu đó trở

thành đối xứng. Đáp số của bài toán sẽ là L(1,n) với n là số kí tự của S. Ta có

công thức sau để tính L(i,j):

L(i,i)=0.

L(i,j)=L(i+1,j - 1) nếu S[i]=S[j]

L(i,j)=max(L(i+1,j), L(i,j - 1)) nếu S[i] ≠ S[j] ;

Ta có thuật toán đơn giản hơn như sau:

Gọi P là xâu đảo của S và T là xâu con chung dài nh ất của S và P. Khi đó

các kí tự của S không thuộc T cũng là các kí tự cần thêm vào để S trở thành đối

xứng.  Đáp  số  của  bài  toán  sẽ  là  n -  k,  với  k  là  độ  dài  của  T.  Ví  dụ:

S=<b>edbabcd, xâu đảo của S là P=<b>dcbabde. Xâu con chung dài nh ất của S

và P là T=<b>dbabd. Như v ậy cần thêm 2 kí tự là e và c vào để S trở thành xâu

đối xứng;

*Chương trình cài đặt:

program xau_doi_xung;

background image

48

uses crt;

const   fi='palin.inp';

 fo='palin.out';

        max=5000;

type    arrA=array[1..max]of char;

        arrB=array[1..max,1..max]of  longint;

var     a:arrA;

        l:arrB;

        f:text;

        n: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]);

        close(f);

end;

function min2so(x,y:longint):longint;

begin

        if x>y then min2so:=y

        else min2so:=x;

end;

procedure       process;

var   i,j:longint;

background image

49

begin

        for i:=1 to n do

                l[i,i]:=0;

        for i:=1 to n-1 do

                for j:=1 to n-i do

                        begin

                                if a[j]=a[j+i]then

                                begin

       if i=1 then l[j,j+i]:=0

                                        else

                                        l[j,j+i]:=l[j+1,j+i -1];

                                end

                                else

                                        l[j,j+i]:=min2so(l[j+1,j+i],l[j,j+i-1])+1;

                        end;

end;

procedure       inkq;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,l[1,n]);

        close(f);

end;

BEGIN

init;

process;

background image

50

inkq;

END.

Bài 11: Bài toán chia kẹo

Có N gói kẹo, gói thứ i có A

i

 cái kẹo. Không được bóc bất kỳ một gói kẹo

nào, cần chia N gói kẹo thành hai phần sao cho độ chênh lệch số kẹo giữa hai

gói là ít nhất.

Dữ liệu:Vào từ file văn bản "chiakeo.inp" có dạng :

-  Dòng đầu tiên là số N (N<=100);

-  Dòng thứ hai là N số A

i

 (i=1, 2,.., N; A

i

 <=100).

Kết quả: Ghi ra file văn bản "chiakeo.out" có dạng:

-  Dòng đầu là độ chênh lệch nhỏ nhất giữa hai phần có thể được.

-  Dòng thứ hai là các chỉ số của của các phần tử trong dãy a thuộc phần

1 ;

-  Dòng thứ ba là các chỉ số của các phần tử trong dãy a thuộc phần 2 ;

Ví dụ:

CHIAKEO.INP

CHIAKEO.OUT

8
19 5 20 13 16 20 18 2

1 56 57
3 5 6
1 2 4 7 8

*Hướng dẫn:

Gọi T là tổng số kẹo của n gói. Chúng ta cần tìm số S lớn nhất thoả mãn:

S ≤ T/2.

Có  một  dãy  con  của  dãy  a  có  tổng  bằng  S.  Khi  đó  sẽ  có  cách  chia  với

chênh lệch 2 phần là T- 2S là nhỏ nhất và dãy con có tổng bằng S ở trên gồm

các phần tử là các gói kẹo thuộc phần thứ nhất. Phần thứ hai là các gói kẹo

còn lại.

* Chương trình cài đặt:

program         chia_keo;

background image

51

uses  crt;

const   fi='chiakeo.inp';

        fo='chiakeo.out';

        max=100;

var     a:array[1..max]of integer;

        n:integer;

        t,k:integer;

        s:array[0..max,0..10000]of integer;

        f:text;

        t1,lech:integer;

        cx:array[1..max]of boolean;

procedure       init;

var i:integer;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                read(f,a[i]);

        close(f);

        k:=0;

        for i:=1 to n dok:=k+a[i];

 t:=k div 2;

        for i:=1 to n do s[i,0]:=1;

        for i:=1 to t do s[0,i]:=0;

        for i:=1 to n do cx[i]:=true;

background image

52

end;

procedure       process;

var i,j:integer;

begin

      for i:=1 to n do

        for j:=1 to t do

                begin

              s[i,j]:=0;

                        if s[i-1,j]=1 then s[i,j]:=1;

                        if j>=a[i] then

                                if s[i-1,j-a[i]]=1 then s[i,j]:=1;

                end;

end;

procedure xu_li;

var i,j:integer;

begin

    for i:=t downto 1 do

                begin

                for j:=1 to n do

                        if s[j,i]=1 then

                        begin

                                t1:=i;

                                lech:=k-2*i;

           exit;

                        end;

                end;

background image

53

end;

procedure       truy_ket_qua;

var  tam,i,t2:integer;

begin

tam:=t1;

t2:=n;

        repeat

        for i:=1 to t2 do

                if s[i,tam]=1 then

                begin

             t2:=i;

                        tam:=tam-a[i];

                        cx[i]:=false;

                        break;

                end;

        until (tam=0);

end;

procedure       inkq;

var i:integer;

begin

assign(f,fo);

rewrite(f);

writeln(f,lech,' ',t1,' ',k-t1);

for i:=1 to n do if cx[i]=false  then write(f,i,' ');

writeln(f);

for i:=1 to n do if cx[i]=true then write(f,i,' ');

background image

54

close(f);

end;

BEGIN

init;

process;

xu_li;

truy_ket_qua;

inkq;

END.

Bài 12: Mua cá theo lýợng (Olympic Balkan 2000) ;

Người đánh cá Clement bắt được n con cá, khối lượng mỗi con là a

i

, đem

bán ngoài chợ. Ở chợ cá, người ta không mua cá theo t ừng con mà mua theo

một lượng nào đó và một con cá bất kì không được cắt ra. Chẳng hạn 3 kg,

5kg...

Ví dụ: có 3 con cá, khối lượng lần lượt là: 3, 2, 4. Mua lượng 6 kg sẽ phải

lấy con cá thứ 2 và và thứ 3. Mua lượng 3 kg thì lấy con thứ nhất. Không thể

mua lượng 8 kg.

Nếu bạn là người đầu tiên mua cá, có bao nhiêu lượng bạn có thể chọn?

Dữ liệu vào: Đọc từ file văn bản MARKET.INP có cấu trúc:

-

Dòng đầu tiên ghi số nguyên n (n <= 10

4

) ;

-

Các dòng tiếp theo ghi các giá trị a

i

 (i=1,2,..,n)

Kết quả ra: Ghi ra file văn bản MARKET.OUT ghi số l là số lượng tìm được.

Ví dụ:

MARKET.INP

MARKET.OUT

3
3  2  4

7

background image

55

* Hướng dẫn:

Thực chất bài toán là tìm các số S mà có một dãy con của dãy a có tổng

bằng S. Ta có thể dùng phương pháp đánh dấu của bài chia kẹo ở trên rồi đếm

các giá trị t mà L[t]=1.

* Chương trình cài đặt:

program so_ca;

uses crt;

const   fi='market.inp';

        fo='market.out';

        maxn=10000;

        max=1000000;

type    arrA=array[1..maxn]of longint;

        arrB=array[0..max]of longint;

var     f:text;

        a:arrA;

        s,dem,n:longint;

        d:arrB;

procedure       init;

var   i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        s:=0;

        for i:=1 to n do

                begin

background image

56

                        read(f,a[i]);

                        s:=s+a[i];

                end;

        close(f);

end;

procedure       process;

var     i,j:longint;

begin

        for i:=1 to s do

                d[i]:=0;

        d[0]:=1;

        for i:=1 to n do

                for j:=s downto a[i] do

                        begin

                        if (d[j]=0) and (d[j-a[i]]=1) then

                                begin

                                        d[j]:=1;

                                        inc(dem);

                                end;

                        end;

end;

procedure       inkq;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,dem);

background image

57

        close(f);

end;

Bài 13: Điền dấu hỏi vào biểu thức

Cho n số tự nhiên a

1

,a

2

,...,a

n

. Ban đầu các số được đặt liên tiếp theo đúng

thứ tự cách nhau bởi dấu '?': a

1

?a

2

?...?a

n

. Cho trước số nguyên S, có cách nào

thay các dấu '?' bằng dấu + hay dấu - để được một biểu thức số học cho giá trị

là S không?

Dữ liệu vào: Đọc từ file văn bản DAUHOI.INP có cấu trúc như sau:

Dòng đầu ghi 2 số nguyên dương n và S (n <=10

4

, S<=10

5

);

Các dòng tiếp theo ghi n giá trị a

1

, a

2

, …, a

n

Kết quả ra: Ghi ra file văn bản DAUHOI.OUT có cấu trúc như sau:

Dòng đầu ghi 0 hoặc 1 tương ứng là không có cách thay các d ấu hỏi hoặc

có tồn tại cách thay dấu hỏi bằng dấu + hay - để giá trị của biểu thức bằng S;

Nếu dòng 1 ghi số 1 thì dòng tiếp theo ghi 1 cách thay  dấu hỏi bởi phép

toán + hoặc - tìm được (chỉ cần ghi các dấu theo thứ tự từ trái sang phải)

Ví dụ:

DAUHOI.INP DAUHOI.OUT

5  20

4 5 6 7 8

1

-+++

*Hướng dẫn:

Đặt L(i,t)=1 nếu có thể điền dấu vào i số đầu tiên và cho kết quả bằng t.

Ta có công thức sau để tính L:

L(1,a[1]) =1.

L(i,t)=1 nếu L(i - 1,t+a[i])=1 hoặc L(i - 1,t - a[i])=1.

Nếu L(n,S)=1 thì  câu trả lời của bài toán là có. Khi cài đặt, có thể dùng

một mảng 2 chiều (lưu toàn bộ bảng phương án) hoặc 2 mảng một chiều (để

background image

58

lưu dòng i và dòng i - 1). Chú ý là chỉ số theo t của các mảng phải có cả phần âm

(tức là từ - T đến T, với T là tổng của n số), vì trong bài này chúng ta dùng c ả

dấu - nên có thể tạo ra các tổng âm.

Bài này có một biến thể là đặt dấu sao cho kết quả là một số chia hết cho

k. Ta có thuật giải tương tự bài toán trên bằng cách thay các phép cộng, trừ

bằng các hép cộng và trừ theo môđun k và dùng mảng đánh dấu với các giá trị

từ 0 đến k - 1 (là các số dư có thể có khi chia cho k). Đáp số của bài toán là

L(n,0).

* Chương trình cài đặt:

program     dau_hoi;

uses crt;

const   fi='daudoi.inp';

        fo='dauhoi.out';

        max=10000;

        maxs=100000;

type    arrA=array[1..max]of longint;

        arrB=array[0..maxs]of longint;

var     f:text;

        n,s,tong:longint;

        a:arrA;

        d:arrB;

        ok:boolean;

        truoc:arrA;

        co:array[1..max]of boolean;

procedure       init;

background image

59

var

    i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n,s);

        tong:=0;

   for i:=1 to s do

            begin

            read(f,a[i]);

            tong:=tong+a[i];

            co[i]:=true;

            end;

        ok:=true;

        if tong<s then ok:=false;

        tong:=tong-s;

        if  tong mod 2 <>0 then     ok:=false

       else tong:=tong div 2;

        close(f);

end;

procedure       process;

var     i,j:longint;

begin

        for i:=1 to tong do

            d[i]:=0;

        d[0]:=1;

background image

60

        for i:=1 to n do

                for j:=tong downto a[i] do

       begin

                        if (d[j]=0) and (d[j-a[i]]=1) then

                                begin

                                        d[j]:=1;

                                        truoc[j]:=i;

                                end;

                 if d[tong]<>0 then exit;

                        end;

end;

procedure       inkq;

var    luu,i:longint;

begin

        assign(f,fo);

        rewrite(f);

        if (d[tong]=0) or (truoc[tong]=1) then

        ok:=false

        else

  begin

                        s:=tong;luu:=truoc[s];

                        while s<>a[luu] do

                        begin

                                co[luu]:=false;

                                s:=s-a[luu];

                                luu:=truoc[s];

background image

61

                        end;

                        co[luu]:=false;

            end;

        if ok then

        begin

        writeln(f,'1');

        for i:=2 to n do

            begin

            if co[i] then write(f,'+')

            else write(f,'-');

            end;

        end

        else write(f,'0');

        close(f);

end;

BEGIN init;

if ok then process; inkq;

END.

Bài 14: Chia thành hai nhóm có tích lớn nhất (ACM 10690)

Cho n số nguyên. Hãy chia chúng thành 2 nhóm sao cho tích c ủa tổng 2

nhóm là lớn nhất.

Dữ liệu vào: Đọc từ file văn bản EXPRESSION.INP có cấu trúc như sau:

-

Dòng đầu ghi 1 số nguyên dương n (n <=10

4

);

-

Các dòng tiếp theo ghi n giá trị a

1

, a

2

, …, a

n

Kết quả ra: Ghi ra file văn bản EXPRESSION.OUT có cấu trúc như sau:

-

Giá trị S là tích lớn nhất của tổng 2 nhóm t ìm được;

background image

62

Ví dụ:

EXPRESSION.INP

EXPRESSION.OUT

5

1  2  3  4  5

56

*Hướng dẫn:

Gọi T là tổng n số nguyên đó. Giả sử ta chia dãy thành 2 nhóm, gọi S là

tổng của một nhóm, tổng nhóm còn lại là T - S và tích của tổng 2 nhóm là S*(T -

S).  Bằng  phương  pháp  đánh  dấu  ta  xác  định  được  mọi  số  S  là  tổng  của  một

nhóm (như bài Market) và t ìm số S sao cho S*(T - S) đạt max.

*Chương trình cài đặt:

uses crt;

const   fi='expression.inp';

        fo='expression.out';

        max=10000;

        maxs=100000;

type    arrA=array[1..max]of longint;

        arrB=array[0..maxs]of longint;

var     f:text;

        n,s,tong:longint;

        a:arrA;

d:arrB;

        ok:boolean;

procedure       init;

var     i:longint;

background image

63

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                begin

                        read(f,a[i]);

                        tong:=tong+a[i];

                end;

        close(f);

end;

procedure       process;

var     i,j:longint;

begin

         for i:=1 to s do

                d[i]:=0;

         d[0]:=1;

         for i:=1 to n do

                for j:=s downto a[i] do

     begin

                        if (d[j]=0) and (d[j-a[i]]=1) then

                                        d[j]:=1;

                        if d[s]<>0 then begin ok:=true;exit;end;

                        end;

end;

procedure       xu_li;

background image

64

begin

        s:=tong div 2 ;

        while not ok do process;

end;

procedure       inkq;

begin

        assign(f,fo);

        rewrite(f);

        if n<>1 then

        write(f,(s+1)*(tong-s-1))

        else writeln(f,'0');

        close(f);

end;

BEGIN

init;

if n<>1 then xu_li;

inkq;

END.

Bài 15: Bài toán mua bán hàng

Có một người đi mua hàng, anh ta có N đồng tiền d

1

, d

2

,.., d

N

. Người bán

hàng có M đồng tiền b

1

, b

2

,.., b

m

. Anh ta muốn mua một mặt hàng với giá trị W.

Hỏi cuộc mua bán có thể diễn ra được không?

Giới hạn: M, N<=100 và d

i

, b

j

<=100.

Dữ liệu vào đọc từ file văn bản MUABAN.INP có cấu trúc như sau:

-

Dòng đầu ghi 3 số nguyên dương N, M, W

-

Dòng thứ 2 ghi N số d

1

, d

2

,.., d

N

;

background image

65

-

Dòng thứ 3 ghi M số b

1

, b

2

,.., b

m

;

Kết quả ghi ra file văn bản MUABAN.OUT có cấu trúc như sau:

-

Dòng đầu ghi số 0 hoặc 1 tương ứng là không thể diễn ra cuộc mua

bán hoặc có thể diễn ra cuộc mua bán;

-

Nếu dòng đầu là 1 thì dòng 2 ghi chỉ số các đồng tiền mà người mua

hàng đã sử dụng để trao đổi, còn dòng 3 ghi chỉ số các đồng tiền mà

người bán hàng đã sử dụng để trao đổi;

Ví dụ:

MUABAN.INP

MUABAN.OUT

MUABAN.INP

MUABAN.OUT

6    10    30

5  7  6 11  2  26

1  2  3  5  6  7

8  9  10

1

1  2  3  4  5

1

4   3    30

6 11  2  26

10  20  30

0

* Chương trình cài đặt:

program     mua_ban_hang;

uses crt;

const   fi='muaban.inp';

        fo='muaban.out';

        max=100;

        maxs=10000000;

type    arrA=array[1..max]of longint;

        arrB=array[0..maxs]of longint;

var     f:text;

        a,b:arrA;

        d:arrB;

        n,m,w,tong,luu1,luu2,s,v:longint;

        ok1,ok2,ok:boolean;

background image

66

        truoc1,truoc2:arrA;

procedure       init;

var i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n,m,w);

        tong:=0;

        for i:=1 to n do

            begin

                read(f,a[i]);

                tong:=tong+a[i];

            end;

        readln(f);

        for i:=1 to m do

                read(f,b[i]);

        close(f);

end;

procedure       process1;

var     i,j:longint;

begin

        for i:=1 to s do

            begin

                d[i]:=0;

                truoc1[i]:=0;

            end;

background image

67

        d[0]:=1;

        for i:=1 to n do

                for j:=s downto a[i] do

                        begin

                        if (d[j]=0) and (d[j-a[i]]=1) then

                                begin

                                        d[j]:=1;

                                        truoc1[j]:=i;

                                end;

                        if d[s]<>0 then begin ok1:=true;exit;end;

            end;

end;

procedure       process2;

var     i,j:longint;

begin

        d[0]:=1;

        for i:=1 to v do

            begin

                d[i]:=0;

                truoc2[i]:=0;

            end;

        for i:=1 to m do

                for j:=v downto b[i] do

                        begin

                        if (d[j]=0) and (d[j-b[i]]=1) then

                                begin

background image

68

                                        d[j]:=1;

                                        truoc2[j]:=i;

                      end;

                        if d[v]<>0 then begin ok2:=true;  exit;end;

                        end;

end;

procedure       xu_li;

begin

    ok:=false;

    for s:=w to tong do

        begin

                ok1:=false;

                process1;

                if ok1 then

                    begin

                        ok2:=false;

                        v:=s-w;

                        if v=0 then ok2:=true

                        else

                            process2;

        end;

                if ok1 and ok2 then

                    begin

                        luu1:=s;

                        luu2:=v;

                        ok:=true;

background image

69

                        break;

                    end;

        end;

end;

procedure       inkq;

var i:longint;

begin

        assign(f,fo);

        rewrite(f);

        if ok then

        begin

            writeln(f,'1');

            i:=truoc1[luu1];

            while luu1<>a[i] do

                begin

                    write(f,i,' ');

                    luu1:=luu1-a[i];

                    i:=truoc1[luu1];

                end;

            writeln(f,i);

            if luu2<>0 then

            begin

           i:=truoc2[luu2];

            while luu2<>b[i] do

                begin

               write(f,i,' ');

background image

70

                    luu2:=luu2-b[i];

                    i:=truoc2[luu2];

                end;

            write(f,i);

            end;

        end

        else writeln(f,'0');

        close(f);

end;

BEGIN

init;

xu_li;

inkq;

END.

Bài 16: Lịch thuê nhân công

Có một dự án kéo dài trong T tháng và ngư ời quản lý cần phải lập lịch sử

dụng công nhân trong dự án, anh ta biết số công nhân tối thiểu cần trong mỗi

tháng. Mỗi khi thuê hay sa thải một công nhân thì đều phải mất một chi phí

xác định, mỗi công nhân được thuê sẽ vẫn nhận được lương tháng ngay cả khi

không sử dụng anh ta làm việc.

Với mỗi công nhân, người quản lý biết chi phí thuê, chi phí sa thải và tiền

lương phải trả cho công nhân đó trong 1 tháng. Và bài toán đ ặt ra như sau: Cần

phải thuê hay sa thải bao nhiêu công nhân mỗi tháng để tổng chi phí dành cho

nhân công của dự án là nhỏ nhất, tức là giảm tối đa chi phí của dự án.

Dữ liệu vào: Đọc từ file văn bản EMPLOY.IN có cấu trúc như sau:

- Dòng đầu ghi T là số tháng diễn ra dự án (T=<100).

background image

71

- Dòng thứ hai ghi 3 số lần lượt là chi phí thuê, lương tháng, chi phí sa th ải

mỗi công nhân.

- Dòng cuối cùng là T số nguyên dương, số thứ i cho biết số công nhân tối

thiểu cần cho tháng thứ i, các giá trị số không quá 150.

Kết quả ra: Ghi ra file văn bản EMPLOY.OUT theo định dạng:

- Dòng thứ nhất ghi tổng chi phí nhỏ nhất tìm được.

- Dòng thứ hai ghi T số, số thứ i là số công nhân hoạt động trong dự án tại

tháng thứ i.

Ví dụ: về file dữ liệu vào và file kết quả ra:

EMPLOY.IN

EMPLOY.OUT

3

4 5 6

10 9 11

265

10 10 11

* Hướng dẫn:

Đề  bài  này  hơi rắc  rối  khó  hiểu  một  chút  vì  vậy  cần  phải  đọc  kỹ,  nắm

chắc. Rõ ràng việc thuê thêm hay sa thải công nhân đều phải chịu phí tổn nên

thấy: để đảm bảo chi phí ít nhất cho việc thuê nhân công trong cả dự án thì số

công nhân đang thuê trong một tháng không nhất thiết phải là số công nhân tối

thiểu cần cho tháng đấy. Ví dụ, phí tổn để thuê cũng như sa thải công nhân rất

lớn thì không dại gì ta lại thường xuyên thuê rồi lại sa thải người trong từng

tháng,  tốt  nhất  nên  giữ  họvà  trả lương (dù họ  không  làm  gì).  Nhiệm  vụ  của

chúng ta trong mỗi tháng phải quyết định có bao nhiêu công nhân trong biên

chế thuê, nghĩa là phải quyết định xem cần thuê hay cần sa thải bao nhiêu công

nhân trong biên chế của tháng trước. Nếu gọi T_max là số công nhân của tháng

cần nhiều người nhất thì rõ ràng số công nhân trong biên chế thuê của một

tháng bất kỳ trong dự án không bao giờ vượt quá T_max. Xét một tháng nào đó,

background image

72

biết rằng không nên sử dụng quá T_max người và cũng  không  được phép sử

dụng ít hơn số người tối thiểu cần cho tháng đấy, nhưng phải chọn giá trị nào

trong khoảng giới hạn này. Đến đây nếu các giá trị của T và T_max là nhỏ thì có

thể duyệt tìm ra kết quả tối ưu nhưng do cácgiá trị này lại có thể khá lớn nên

buộc phải tìm cách khác hiệu quả hơn.

Lập mảng Scn[1..T], Scn[i] cho biết số công nhân tối thiểu cần cho tháng

thứ  i,  i=1..T(mảng  này  nhập  vào  từ  file  input).  Lập  thêm  mảng  C[T,T_max]

trong đó C[i,j] cho biết chi phí tối thiểu của i tháng đầu tiên của dự án nếu tại

tháng thứ i có j công nhân trong biên chế thuê (i=1..T, =1..T_max). Thấy là giá

trị C[i,j] cóthể xác định thông qua các giá trị C tại tháng i-1 (là tháng trước):

C[i,j] = Min{C[i-1,k] + chi phí để từ k người thành j người }

( i=1..T;j=Scn[i]..T_max; k=Scn[i -1]..T_max)

Bài  toán  đã đơn giản  hơn  nhiều  khi  có  được  công  thức  truy  hồi  và  vì độ

phức tạp tính toán không lớn nên chắc chắn chương trình sẽ ngắn gọn, cho kết

quả tối ưu trong thời gianrất ngắn. Kết quả tối ưu cuối cùng là:

Kq = Min{C[T,j]+ chi phí sa thải j người}

(j=Scn[T]..T_max)

Để có thể đưa ra kết quả đầy đủ(số công nhân sử dụng mỗi tháng) thì cần

thêm  mảng  hai  chiều  Pre,  trong  đóPre[i,j]  :=  k,  v ới  i=1..T;  j=Scn[i]..T_max;  k

thoả mãn tổng (C[i-1,k] + chi phí để từ k người thành j người) nhỏ nhất. Việc

lần ngược tìm kết quả trong mảngPre không khó.

Bài 17: Cắt hình chữ nhật

Ta cần cắt một hình chữ nhật có kích thước M x N (M, N nguyên dương

không lớn hơn 100) thành một số ít nhất các hình vuông có kích thước nguyên

dương và có cạnh song song với cạnh của hình chữ nhật ban đầu. Biết rằng khi

background image

73

cắt một hình chữ nhật bất kỳ chỉ cắt được theo 1 phương song song với một

trong các các cạnh của hình chữ nhật đó.

Dữ liệu vào: Đọc từ file văn bản CUT.INP có một dòng ghi 2 số M, N.

Kết quả ra: Ghi ra file CUT.OUT có cấu trúc như sau:

Dòng thứ nhất ghi số K là số hình vuông ít nhất cắt ra.

Dòng thứ 2 ghi kích thước của K hình vuông cắt ra được, mỗi số cách nhau

1 dấu cách.

Ví dụ:

CUT.INP CUT.OUT

5  6

5

2  2  2  3  3

Mô tả trên hình vẽ: (Số ghi trong hình vuông cho biết kích thước của hình

vuông đó)

*Hướng dẫn:

+ Nếu M = 1 thì cắt được ít nhất bao nhiêu hình vuông thoả mãn?

+ Nếu N = 1 thì cắt được ít nhất bao nhiêu hình vuông thoả mãn?

+ Nếu gọi F[i,j] là số hình vuông ít nhất cắt ra từ hình chữ nhật kích thước

i, j (với 1 < i < M; 1 < j < N) thì công thức QHĐ được xây dựng như thế nào?

Bài toán con cơ sở: F[1,j] := j với mọi j; F[i,1] := i với mọi i

Công thức QHĐ:

Với i := 2, .., m

Với j := 2, .., n

Nếu i = j thì  F[i,i] := 1

2

2

2

3

3

background image

74

Trái lại

 t1 := min{F[k,j] + F[i-k, j]} với k = 1,2, …, i div 2;

 t2 := min{F[i,k] + F[i, j-k]} với k = 1,2, …, j div 2;

F[i,j] := min{t1, t2}

*Chương trình cài đặt:

program cut_hcn;

const   fi='cut.inp';

  fo='cut.out';

        maxn=1001;

var     f:text;

        m,n,t:integer;

        q:array[1..maxn,1..maxn] of integer;

procedure       nhap;

var     i,j:integer;

begin

        assign(f,fi);

        reset(f);

        readln(f,m,n);

        for i:=1 to m do

                for j:=1 to n do

                        q[i,j]:=-1;

        close(f);

end;

procedure       xuli;

var min,l,k,i,j:integer  ;

begin

background image

75

        for i:=1 to m do  q[i,1]:=i;

        for i:=1 to n do   q[1,i]:=i;

        for i:=2 to m do

                for j:=2 to n do

                        if i=j then q[i,j]:=1

                        else

                        begin

                                min:=maxint;

                                for k:=1 to j div 2 do

                                        if min>q[i,k]+q[i,j -k] then    min:=q[i,k]+q[i,j-k] ;

                                for l:=1 to i div 2 do

                                        if min>q[l,j]+q[i -l,j] then   min:=q[l,j]+q[i-l,j] ;

                                q[i,j]:=min;

                        end;

        {if i=-1 then

if (i mod j =0) or (j mod i =0) then

                begin

                        if i>=j then q[i,j]:=i div j   else q[i,j]:=j div i;

                end

        else

                        begin

                                min:=maxint;

                    for k:=1 to j div 2 do

                                        if min>tinhq(i,k)+tinhq(i,j -k) then min:=tinhq(i,k)+tinhq(i,j -

k) ;

                                for l:=1 to i div 2 do

background image

76

                                        if min>tinhq(l,j)+tinhq(i-l,j) then

                                                min:=tinhq(l,j)+tinhq(i -l,j) ;

                                q[i,j]:=min;

                        end;

        tinhq:=q[i,j]; }

end;

procedure       timkq(i,j:integer);

var k,l,tg:integer;

begin

        if (i mod j =0) or (j mod i =0) then

                begin

                        if i>=j then for tg:=1 to i div j do write(f,j,' ')

                        else for tg:=1 to j div i do write(f,i,' ');

                end

        else

        begin

        for k:=1 to (j div 2) do

                if q[i,j]=(q[i,k]+q[i,j-k]) then

                        begin

                                timkq(i,k);

                                timkq(i,j-k);

                                exit;

                        end ;

        for l:=1 to (i div 2) do

                if q[i,j]=(q[l,j]+q[i-l,j]) then

                        begin

background image

77

                                timkq(l,j);

                                timkq(i-l,j);

         exit;

                        end;

        end;

end;

procedure       xuat;

var j,i:integer;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,q[m,n]);

        timkq(m,n);

        close(f)

end;

BEGIN

        nhap;

        xuli;

    xuat;

END.

Bài 18: Trò chơi với băng số: Rate This

Trò chơi với băng số là trò chơi tham gia trúng thưởng được mô tả như

sau:  Có  một  băng  hình  chữ  nhật  được  chia  ra  làm n  ô  vuông,  đánh  số  từ  trái

qua phải bắt đầu từ 1. Trên ô vuông thứ i người ta ghi một số nguyên dương a

i

,

i = 1, 2, …, n. Ở một lượt chơi, người tham gia trò chơi được quyền lựa chọn

một số lượng tùy ý các ô trên băng số. Giả sử theo thứ tự từ trái qua phải,

background image

78

người chơi lựa chọn các ô i

1

i

2

, …, i

k

. Khi đó điểm số mà người chơi đạt được

sẽ là:

a

i1

– a

i2

 + … + (-1)

k-1

a

ik

Yêu cầu: Hãy tính số điểm lớn nhất có thể đạt được từ một lượt chơi.

Dữ liệu vào: Đọc từ file văn bản Rate.inp có cấu trúc:

Dòng đầu tiên chứa số nguyên dương n ( n ≤ 10

6

 ) là số lượng ô của băng

số;

Dòng thứ hai chứa n số nguyên dương a

1

, a

2

, …, a

n

 ( a

i

≤ 10

4

, i = 1, 2, …, n )

ghi trên băng số. Các số liên tiếp trên cùng dòng được ghi cách nhau bởi ít nhất

một dấu cách.

Kết quả ra: Ghi ra file văn bản Rate.out có cấu trúc là:

Một số nguyên duy nhất là số điểm lớn nhất có thể đạt được từ một lượt

chơi.

Ví dụ:

Rate.inp

Rate.out

7

4  9  2  4  1  3  7

17

Ràng buộc: 60% số tests ứng với 60% số điểm của bài có 1 ≤ n ≤ 20.

*Chương trình cài đặt:

program rate_this;

const   fi='ratethis.inp';

background image

79

        fo='ratethis.out';

     maxn=1000000;

var     f:text;

        n,kq,dem:int64;

        a,fl,fc,ds:array[1..maxn] of int64;

        luuc,luul:array[1..maxn] of integer;

procedure       nhap;

var     i:longint;

begin

        assign(f,fi);

        reset(f);

        readln(f,n);

        for i:=1 to n do

                read(f,a[i]) ;

        close(f);

        dem:=0;

        fillchar(luul,sizeof(luul),0);

        fillchar(luuc,sizeof(luuc),0);

end;

function        max2so(cl,j,x,y:int64):int64;

begin

        if x>y  then   max2so:=x

        else    begin

                        max2so:=y;

background image

80

                        if cl=0 then luuc[j]:=1;

                        if cl=1 then luul[j]:=1;

                end;

end;

procedure       xuli;

var i,chanle:longint;

begin

      fl[1]:=a[1];

     fc[1]:=0;

      for i:=2 to n do

        begin

              fl[i]:=max2so(1,i,fl[i-1],fc[i-1]+a[i]);

              fc[i]:=max2so(0,i,fc[i-1],fl[i-1]-a[i]);

        end;

      if fl[n]>fc[n] then begin kq:=fl[n];chanle:=1;end

      else begin kq:=fc[n];chanle:=0;end;

      for i:=n downto 1 do

        begin

                if (chanle=1) and (luul[i]=1) then

                        begin

                                inc(dem);

                                ds[dem]:=i;

 chanle:=0;

                                continue;

                        end;

background image

81

                if (chanle=0) and (luuc[i]=1) then

                        begin

                              inc(dem);

                              ds[dem]:=i;

                    chanle:=1;

                        end;

        end;

end;

procedure       xuat;

var i:longint;

begin

        assign(f,fo);

        rewrite(f);

        writeln(f,kq);

        for i:=dem downto 1 do

                write(f,ds[i],' ');

      close(f)

end;

BEGIN

        nhap;

        xuli;

        xuat;

END.

background image

82

Bài 19: Con kiến

Trên  một  sân  hình  chữ  nhật MxN, được chia thành các ô vuông đơn v ị,

mỗiô chứa một lượng thức ăn. Một con kiến xuất phát từ ô (1,1) muốn đi qua

sân để đến dòng thứ M. Con kiến chỉ có thể đi theo một dòngchia nhỏ trên sân

ứng với một dòng của bảng chữ nhật hoặc đi theotrên một cột của sân. Hãy chỉ

ra đường đi giúp con kiến có được nhiều thức ăn nhất.

Dữ liệu vào: Đọc từ file văn bản FOOD.INP có cấu trúc:

Dòng đầu là 2 số M, N.

M dòng tiếp theo, mỗi dòng có N số tương ứng là lượng thức ăn có trong

M x N ô trên sân.

Kết quả ra: Ghi ra file văn bản FOOD.OUT có cấu trúc:

Dòng đầu là tổng lượng thức ăn nhiều nhất mà con kiến có thể ăn được.

Các dòng tiếp theo, mỗi dòng là 2 số tương ứng là toạ độ dòng, cột của ô

mà con kiến đi qua.

Ví dụ:

FOOD.INP

3 5

FOOD.OUT

45

1  1

2  1

2  2

2  3

background image

83

3  3

*Hướng dẫn:

Trọng số ở đây là lượng thức ăn trên mỗi ô.

Quy tắc đi:

Dễ dàng tìm được công thức quy hoạch  động là:

Gọi  B[i,j]  là  lượng  thức  ăn  lớn  nhất  đi  từ  ô  (1,1)  đến  ô  (i,j)

B[1,j]= A[1,j] với j = 1..N

B[i,1]= A[i,1]+B[i-1,1] với i = 2..M

B[i,j]=Max{B[i-1,j],B[i,j-1]} + A[i,j] với i = 2..M và j = 2..N

Bài 20: Sa mạc

Một bãi sa mạc có dạng hình chữ nhật MxN. Mỗi ô vuông đơn vị trên sa

mạc  có  một độ cao nào đó. Một người muốn đi từ  bờ đầu này  sang  bờcuối

cùng bên kia. Người đó chỉ có thể đi từ ô đang đứng tới mộtô mới theo hướng

thẳng  đứng  chéo  trái  hoặc  chéo  phải.  Giả  thiết  rằng  người  đó  không  được

vượt ra hai mép trái và phải của sa mạc.

Hãytìm đường  đi  sao  cho  người  đó  phải  vượt  qua  quãng đường  ngắn

nhất. Mỗi lần đi từ một ô sang ô mới tiếp theo người đó phải đi hết quãng

đường bằng độ chênh cao giữa hai ô đó.

background image

84

SAMAC.INP

SAMAC.OUT

12(Quãng đường Min)

(1,3)(2,4) (3,3) (4,2) (5,2)

*Hướng dẫn:

Trọng số là độ chênh cao giữa hai ô liên tiếp.

Quy tắc đi.

Công thức tối ưu:

B[i,j] là quãng đường nhỏ nhất đi từ bờ đầu tiên đến ô (i,j).

B[1,j]= 0 với j = 1..N

B[i,1]= Min { B[i-1,1] + abs(A[i,1] - A[i-1,1]), B[i-1,2]+ abs(A[i,1] - A[i-1,2])}

Với i = 2..M

B[i,j]= Min { B[i-1,j -1] + abs(A[i,j] - A[i-1,j -1]),B[i-1,j] + abs(A[i,j] - A[i-1,j]),

B[i-1,j+1] + abs(A[i,j] - A[i-1,j+1])} Với i = 2..M, j = 2..N-1

B[i,N]  =  Min {  B[i-1,N]  +  abs(A[i,N] - A[i-1,N]),  B[i-1,N-1]+  abs(A[i,N] -  A[i-

1,N-1])}, Với i = 2..M.

background image

85

Bài 21: Quầy bán hàng.

Một siêu thị có M gian hàng, mỗi gian hàng gồm N ngăn chứa, mỗi ngăn

chứa  được  bố  trí ở  một  phòng.  Giám đốc  siêu  thị  quyết  định  mở  một  đợt

khuyến mãi cho khách hàng với các quy tắc sau:

Mỗi gian hàng được  bố  trí  trên  từng  tấng tương ứng  từ  tầng 1 đến  M.

Mỗi tầng có N thang máy đi lên ứng với mỗi phòng.

Một khách hàng có thể mua sản phẩm tại một gian hàng nhưng chỉ có thể

đi  theo  một  hướng  (không  được  mua  xong  rồi  quay  trở  lại  nơi  đã  mua).

Khách hàng có thể đi thang máy lên tầng tiếp theo, nhưng phải mua ít nhất tại

một ngăn chứa ở tầng đó thì mới được phép đi lên tầng trên nữa.

Khách hàng mua hàng tại một ngăn chứa. Mỗi ngăn chứa quy định một số

lượng  hàng  mà  người  khách  buộc  phải  mua  khi  đến  ngăn  chứa  đó.

Nếu độ chênh số lượng hàng giữa hai ngăn chứa liên tiếp của một khách hàng

là  một số may mắn đã biết trước. Khách hàng đó sẽ được khuyến mãi thêm

một số hàng bằng chính số may mắn đó.

Đến tầng thứ M, khách hàng chỉ có thể mua hàng tại duy nhất một ngăn

chứa.

Hãy giúp khách hàng lựa chọn điểm xuất phát và hướng đi sao cho mua

được nhiều hàng nhất (Kể cả số hàng được khuyến mãi).

Dữ liệu đầu vào cho trong FILE văn bản SHOP.INPcó cấu trúc như sau:

Dòng đầu là 3 số M,N, K (1<M, N, K ≤100), K là số các con số may mắn.

Dòng thứ hai ghi K con sốmay mắn.

M  dòng  tiếp  theo  ghi  số  lượng  hàng  quy  định  tại  mỗi  ngăn  chứa.  Mỗi

dòng gồm N số cách nhau bởi ít nhất một dấu trắng.

Kết quả ghi ra FILE văn bản SHOP.OUT như sau:

Dòng một là sốlượng hàng nhiều nhất.

background image

86

Dòng  hai điểm xuất phát và quá trình mua hàng. Mỗi ngăn chứa đi qua

được biểu diễn theo dạng ″(x,y) ″ trong đó (x,y) là vị trí của ngăn chứa.

Ví dụ:

SHOP.INP

SHOP.OUT

80 (Lượng hàng mua được Max)

(1,3) (1,2) (2,2) (2,1) (3,1) (3,2) (3,3) (3,4) (4,4)

*Hướng dẫn:

Trọng số ở đây là số lượng hàng tại các ngăn chứa. Đồng thời khi thoả

mãn điều kiện ″khuyến mãi ″ thì trọng số sẽ được tăng thêmsố lượng bằng số

các con số may mắn (phụ thuộc vào ô đứng trước).

Quy tắc đi:

:

Công thức quy hoạch động:

B[i,j]  là  lượng  hàng  max  khi  đi  t ừ  tầng  1  cho  đến  ngăn  chứa  (i,j)

B[0,j]= 0 với j =1..N

B[i,0]= 0 với i =1..M

background image

87

B[i,j]= Max{B[i,j-1]+KM1,  B[i-1,j] + KM2,  B[i-1,k]+ SA[i,u]+KM3} + A[i,j]. Với

=1..M 

=1..(N -1) 

=(j+1)..M 

j+1..k

KM1  là  lượng  hàng  khuyến  mãi  nếu  Abs(A[i,j]-A[i,j-1])  là  con  số  may  mắn.

KM2  là  lượng  hàng  khuyến  mãi  nếu  Abs(A[i,j]-A[i-1,j])  là  con  số  may  mắn.

KM3  là  tổng  số  lượng  hàng  khuyến  mãi  nếu  Abs(A[i,k]-A[i-1,k])  vàAbs(A[i,t]-

A[i,t+1]) 

với 

j..(u-1) 

là 

các 

con 

số 

may 

mắn.

B[i,N]= Max{B[i,N-1]+KM1,B[i-1,N]+KM2}+A[i,N]

Với i =1..M

KM1  là  lượng  hàng  khuyến  mãi  nếu  abs(A[i,N]-A[i,N-1])  là  con  số  may

mắn.

KM2  là  lượng  hàng  khuyến  mãi  nếu  abs(A[i,N]-A[i-1,N])  là  con  số  may  mắn.

Nhận xét:

Còn rất nhiều bài toán khác có dạng như một trong các  bài toán cơ bản

này nhưng chung quy lại chúng ta đều có thể đưa nó về một dạng chung. Sau

đó    dựa vào  những  nguyên  tắc  giải  chung,  ta  đều  có  thể  giải  quyết  dễ  dàng.

Các dạng bài toán tổng quát này khi dữ liệu cho quá giới hạn khai báo bảng hai

chiều  đều  có  thể  giải  quyết  bằng  cách  quy  hoạch  liên  tục  trên  2mảng  một

chiều. Sau mỗi bước quy hoạch phải thay đổi 2 mảng này saocho phù hợp với

bước quy hoạch tiếp theo. Cái khó của bài toán có dữ liệu lớn này là việc lưu

trữ trạng thái để sau khi quy hoạch toàn bộ ta còn có thể in ra file kết quả ″quá

trình đi ″ của phương án tối ưu.

background image

88

Chương IV.

Một số đề tự giải

Bài 22: Chia thành nhiều nhóm có tổng bằng nhau

Cho n số a

1

, a

2

,..., a

n

 (n ' NI*). Tìm cách chia số trên thành m nhóm sao cho

mỗi nhóm đều có tổng bằng nhau và m tìm được là lớn nhất.

Dữ liệu vào: Đọc vào từ file văn bản MNHOM.INP có cấu trúc như sau :

Dòng đầu ghi số n nguyên dương (N <= 10

4

)

Các dòng tiếp theo ghi n số a

1

, a

2

,..., a

n

 , các số cách nhau ít nhất 1 dấu

cách;

Kết quả ra: Ghi ra file văn bản: MNHOM.OUT có cấu trúc như sau:

Dòng đầu tiên ghi số m lớn nhất tìm được

M dòng tiếp theo, mỗi dòng ghi các chỉ số của các phần tử thuộc cùng 1

nhóm;

Ví dụ:

MNHOM.INP

MNHOM.OUT

7

8 2 1 3 9 10 11

4

1  4

2  5

3  6

7

Bài 23: Bài toán xoay DOMINO

Cho N thanh DOMINO xếp theo chiều dọc như hình vẽ.

Ví dụ hình trên gồm 5 thanh DOMINO.

background image

89

Mỗi thanh DOMINO gồm 2 phần, phần trên và phần dưới. Trên mỗi phần

có một số từ 1 đến 6. Yêu cầu đặt ra là hãy tìm cách xoay các thanh (xoay 180

độ) để sau khi xoay chênh lệch giữa tổng trên và tổng dưới là ít nhất. Giới hạn:

N<=1000.

Dữ liệu vào: Đọc từ file văn bản DOMINO.INP có cấu trúc như sau:

-

Dòng đầu tiên ghi số nguyên dương N

-

N dòng tiếp theo, mỗi dòng i ghi 2 số nguyên a

i

, b

i

 (1 <=a

i

,b

i

 <= 6)

tương ứng là số ở trên, ở dưới của thanh DOMINO thứ i;

Kết quả ra: Ghi ra file văn bản DOMINO.OUT có cấu trúc như sau:

-

Dòng đầu tiên ghi giá trị chênh lệch giữa tổng trên và tổng đưới;

-

Các dòng tiếp theo là chỉ số của các thanh DOMINO đ ược xoay;

Ví dụ:

DOMINO.INP

DOMINO.OUT

5

1  2

1  6

3 5

2  6

6  4

0

1

Bài 24: Bài toán ngân hàng trả tiền;

Một người đi lấy tiền ở một ngân hàng. Anh ta cần lấy một khoản đúng M

đồng. Ngân hàng có N đồng tiền A

1

, A

2

,.., A

N

. Hỏi ngân hàng có bao nhiêu cách

trả tiền.

Dữ liệu vào: Đọc từ file văn bản "MONEY.INP" có dạng:

 Dòng đầu là hai số N và M (N <=100, M <= 10000)

Các dòng tiếp theo là các phần tử của mảng A.

background image

90

Kết quả ra: Ghi ra file văn bản "MONEY.OUT" gồm một dòng duy nhất là

số cách trả tiền (Số cách trả tiền < Maxlongint)

Ví dụ:

Bài 25: Bài toán dãy có tổng chia hết cho k (đề thi toàn Quốc)

Cho  một  dãy  số  nguyên  A

1

,  A

2

,..,  A

N

  và  một  số  k.  Hãy  tìm  một  dãy  con

(không nhất thiết phải liên tiếp nhau) dài nhất có tổng các số chia hết cho số k.

Dữ liệu vào: Đọc từ file văn bản "dayso.inp" có dạng:

Dòng 1 gồm 2 số N và k (N<=1000; k<=50)

Các dòng tiếp theo chứa các số của mảng A.

Kết quả ra: Ghi ra file văn bản "dayso.out" gồm một dòng ghi số phần tử

lớn nhất tìm được.

Ví dụ:

Bài 26: Xếp đồ vật vào ba lô (mỗi loại đồ vật có lýợng không hạn

chế);

Một chiếc ba lô có thể chứa được một khối lượng không quá W. Có N loại

đồ vật được đánh số từ 1 đến N, mỗi đồ vật loại i có khối A

i

 và giá trị sử dụng C

i

(i=1, 2, …, N), số lượng đồ vật mỗi loại là không hạn chế. Hãy chọn các đồ vật

(mỗi loại bao nhiêu cái) để xếp vào ba lô sao cho tổng giá trị các đồ vật trong

ba lô là lớn nhất (nếu có thể xếp được).

Dữ liệu vào: Đọc từ file văn bản BALO.INP có cấu trúc như sau:

Dòng đầu ghi 2 số nguyên dương N, W (0 < N, W < 100).

Dòng i+1, 1 < i < N) ghi 2 số nguyên dương A

i

 và C

i

 (0<A

i

, C

i

<256)

background image

91

Kết quả ra: Ghi ra file văn bản BALO.OUT có cấu trúc như sau:

Dòng đầu ghi tổng giá trị lớn nhất tìm được.

Từ dòng thứ 2, mỗi dòng ghi 2 số là: số hiệu loại đồ vật được chọn và số

lượng loại đồ vật đó.

Ví dụ:

BALO.INP

BALO.OUT

9  100

60  7

8  45

50  15

9  7

7  68

130  57

1  3

70  123

10  95

965

5  10

9  3

Bài 27: Đổi tiền

Ở đất nước Omega người ta chỉ tiêu tiền xu. Có N loại tiền xu, loại thứ i

có mệnh giá là a

i

 đồng. Một người khách du lịch đến Omega du lịch với số tiền

M đồng. Ông ta muốn đổi số tiền đó ra tiền xu Omega để tiện tiêu dùng. Ông ta

cũng muốn số đồng tiền đổi được là ít nhất (cho túi tiền đỡ nặng khi đi đây đi

đó). Bạn hãy giúp ông ta tìm cách đổi tiền.

Dữ liệu: vào trong file văn bản Money.inp có dạng:

-

 Dòng đầu là 2 số N và M (N <=100 và M <=10000)

-

Các dòng tiếp theo là N số tương ứng là các mệnh giá a

1

, a

2

, …,a

n

(với 0 < a

i

 <= 1000)

background image

92

Kết  quả: Ghi  ra  file  văn  bản  Money.out  gồm  một  dòng  duy  nhất  là  số

đồng tiền ít nhất có thể đổi được (Số đồng xu < Maxlongint)

Ví dụ:

Money.inp

Money.out

4  10

1  2  3  4

3

Money.inp

Money.out

5  10

2  4  6  8  9

2

Bài 28:Chia tập

Xét tập chứa các số nguyên từ 1 tới N với N = 4K – 1 hoặc N = 4K. Tập các

số nguyên này có thể chia thành hai tập con có tổng các phần tử bằng nhau. Ví

dụ, với N = 7, ta có 4 cách chia thoả mãn điều kiện trên:

{2,3,4,5} và {1,6,7}

{1,3,4,6} và {2,5,7}

{1,2,5,6} và {3,4,7}

{1,2,4,7} và {3,5,6}

Với N cho trước, hãy tính số cách chia thành 2 tập con có tổng các phần

tử bằng nhau.

Dữ liệu: Vào từ file văn bản SET.INP, mỗi dòng chứa một số nguyên N (N

<= 100);

Kết quả: Đưa ra file văn bản SET.OUT số cách chia, mỗi dòng ứng với 1

dòng của file dữ liệu vào

Ví dụ:

SET.INP

SET.OUT

7

4

background image

93

11

35

Bài 29: Đổi tiền xu

Nước Silverland sử dụng hệ thống tiền xu, trong đó các xu có m ệnh giá là

một số chính phương: 1,4,9,…,289(=17

2

). Với hệ thống này, để trả 10 xu ta có

4 cách:

Trả 10 đồng 1 xu,

Trả 1 đồng 4 xu và 6 đồng 1 xu,

Trả 2 đồng 4 xu và 2 đồng 1 xu,

Trả 1 đồng 9 xu và 1 đồng 1 xu.

Nhiệm vụ của bạn là xác định xem có bao nhiêu cách trả một số tiền cho

trước ở nước Silverland.

Dữ liệu vào: Vào từ file văn bản COIN.INP

Gồm nhiều dòng, mỗi dòng một số nguyên dương không vượt quá 800.

Kết  quả  ra:  Đưa  ra  file  văn  bản COIN.OUT  là  số  cách  trả ứng  với  từng

trường hợp.

Ví dụ:

COIN.INP

2

10

30

COIN.OUT
1
4

27

27

27

27

27

background image

94

MỤC LỤC

I.1. Tư tưởng của phương pháp quy hoạch động.........................................................................3

I.1.1. Thuật toán chia để trị ......................................................................................................3
I.1.2. Hệ thức truy hồi ..............................................................................................................4
I.1.3. Lập trình động là gì?.......................................................................................................5
I.1.4. Phương pháp quy hoạch động ........................................................................................ 7

I.2. Các bước thực hiện giải bài toán bằng phương pháp quy hoạch động .................................8

I.2.1. Các bước cơ bản: ............................................................................................................8
I.2.2. Tổ chức cài đặt: ..............................................................................................................9
I.2.3 Khi nào dùng phương pháp quy ho ạch động? .................................................................9
Bài 1: Tìm dãy con không gi ảm nhiều phần tử nhất; ............................................................. 10

Hướng dẫn: ......................................................................................................................... 10
* Chương trình cài đặt:.......................................................................................................11

Bài 2: Dãy con chung dài nh ất;.............................................................................................. 13

Ví dụ: ..................................................................................................................................14
*Hướng dẫn: ....................................................................................................................... 14
*Chương trình cài đặt:........................................................................................................15

Bài 3: Dãy con có tổng bằng S .............................................................................................. 17
Bài 4: Xếp đồ vật vào ba lô (mỗi đồ vật chỉ có 1) .................................................................20

*Hướng dẫn ........................................................................................................................ 21
*Chương trình cài đặt:........................................................................................................22

Chương III: Bài tập chọn lọc .....................................................................................................26

Bài 5: Bố trí phòng họp; ........................................................................................................26

* Hướng dẫn: ...................................................................................................................... 27
* Chương trình cài đặt:.......................................................................................................27

Bài 6: Cho thuê máy tính; ......................................................................................................30

*Hướng dẫn: ....................................................................................................................... 31
* Chương trình cài đặt:.......................................................................................................31

Bài 7: Nối điểm ...................................................................................................................... 34

*Hướng dẫn: ....................................................................................................................... 35
* Chương trình cài đặt:.......................................................................................................35

Bài 8: Dãy con đổi chiều, đối dấu dài nhất; ...........................................................................38

* Hướng dẫn: ...................................................................................................................... 39
* Chương trình cài đặt:.......................................................................................................39

Bài 9: Số phép biến đổi ít nhất ............................................................................................... 43

* Hướng dẫn: ...................................................................................................................... 43
*Chương trình cài đặt:........................................................................................................44

Bài  10: Xâu đối xứng; ...........................................................................................................47

*Hướng dẫn: ....................................................................................................................... 47
*Chương trình cài đặt:........................................................................................................47

Bài 11: Bài toán chia k ẹo .......................................................................................................50

background image

95

*Hướng dẫn: ....................................................................................................................... 50
* Chương trình cài đặt:.......................................................................................................50

Bài 12: Mua cá theo lýợng (Olympic Balkan 2000) ; ............................................................ 54

* Hướng dẫn: ...................................................................................................................... 55
* Chương trình cài đặt:.......................................................................................................55

Bài 13: Điền dấu hỏi vào biểu thức ....................................................................................... 57

*Hướng dẫn: ....................................................................................................................... 57
* Chương trình cài đặt:.......................................................................................................58

Bài 14: Chia thành hai nhóm có tích l ớn nhất (ACM 10690) ................................................61

*Hướng dẫn: ....................................................................................................................... 62
*Chương trình cài đặt:........................................................................................................62

Bài 15: Bài toán mua bán hàng .............................................................................................. 64

* Chương trình cài đặt:.......................................................................................................65

Bài 16: Lịch thuê nhân công ..................................................................................................70

* Hướng dẫn:...................................................................................................................... 71

Bài 17: Cắt hình chữ nhật ......................................................................................................72

*Hướng dẫn:....................................................................................................................... 73
*Chương trình cài đặt:........................................................................................................74

Bài 18: Trò chơi với băng số: Rate This ................................................................................77

*Chương trình cài đặt:........................................................................................................78

Bài 19: Con kiến .................................................................................................................... 82

*Hướng dẫn: ....................................................................................................................... 83

Bài 20: Sa mạc ....................................................................................................................... 83

*Hướng dẫn: ....................................................................................................................... 84

Bài 21: Quầy bán hàng...........................................................................................................85

*Hướng dẫn:....................................................................................................................... 86

Nhận xét: ................................................................................................................................ 87
Bài 22: Chia thành nhiều nhóm có tổng bằng nhau ............................................................... 88
Bài 23: Bài toán xoay DOMINO ........................................................................................... 88
Bài 24: Bài toán ngân hàng tr ả tiền; ...................................................................................... 89
Bài 25: Bài toán dãy có t ổng chia hết cho k (đề thi to àn Quốc) ............................................90
Bài 26: Xếp đồ vật vào ba lô (mỗi loại đồ vật có lýợng không hạn chế); ............................. 90
Bài 27: Đổi tiền ...................................................................................................................... 91
Bài 28:Chia tập ...................................................................................................................... 92
Bài 29: Đổi tiền xu ................................................................................................................. 93

background image

96

Tài liệu tham khảo

1. Trần Đỗ Hùng, Chuyên đề bồi dưỡng HSG Tin học THPT,NXB GD 2007

2. Nguyễn Quý Khang, Bài tập Pascal, NXB QGHN 2002

3. Nguyễn Xuân My, Một số vấn đề chọn lọc trong môn Tin học, NXB GD

2002

4. Nguyễn Xuân My, Bài tập lập trình Pascal, NXB TK 1997

5. Báo TH&NT 1999 - 2006

6. Trang web:

http://www.wikipedia.org

7. Trang web:

http://www.vnoi.info

8. Trang web:

http://www.ddth.com