Băng nhạc Người ta cần ghi N bài hát, được mã số từ 1 đến N, vào một băng nhạc có thời lượng tính theo phút đủ chứa toàn bộ các bài đã cho.. Tính trung bình, các bài hát trong một ngày
Trang 1Phương pháp tham lam gợi ý chúng ta tìm một trật tự hợp lí để duyệt dữ liệu nhằm đạt được mục tiêu một cách chắc chắn và nhanh chóng Thông thường, dữ liệu được duyệt theo một trong hai trật tự là tăng hoặc giảm dần theo một chỉ tiêu nào đó Một số bài toán đòi hỏi những dạng thức cải biên của hai dạng nói trên
Bài 5.1 Băng nhạc
Người ta cần ghi N bài hát, được mã số từ 1 đến N, vào một băng nhạc có thời lượng tính theo phút đủ chứa toàn bộ các bài đã cho Với mỗi bài hát ta biết thời lượng phát của bài đó Băng sẽ được lắp vào một máy phát nhạc đặt trong một siêu thị Khách hàng muốn nghe bài hát nào chỉ việc nhấn phím ứng với bài đó Để tìm và phát bài thứ i trên băng, máy xuất phát từ đầu cuộn băng, quay băng để bỏ qua i – 1 bài ghi trước bài đó Thời gian quay băng bỏ qua mỗi bài và thời gian phát bài đó được tính là như nhau Tính trung bình, các bài hát trong một ngày được khách hàng lựa chọn với số lần (tần suất) như nhau Hãy tìm cách ghi các bài trên băng sao cho tổng thời gian quay băng trong mỗi ngày là ít nhất
Dữ liệu vào được ghi trong tệp văn bản tên BANGNHAC.INP
- Dòng đầu tiên là số tự nhiên N cho biết số lượng bài hát
- Tiếp đến là N số nguyên dương thể hiện dung lượng tính theo phút của mỗi bài Mỗi đơn vị dữ liệu cách nhau qua dấu cách
Thí dụ dưới đây cho biết có N = 3 bài hát:
- Bài 1 phát trong thời gian 7 phút
- Bài 2 phát trong thời gian 2 phút
- Bài 3 phát trong thời gian 3 phút
Dữ liệu ra được ghi trong tệp văn bản
BANGNHAC.OUT theo dạng thức sau:
- N dòng đầu tiên thể hiện trật tự ghi bài hát trên băng: m ỗi dòng gồm hai số nguyên dương j và d cách nhau bởi dấu cách, trong đó j là mã số của bài hát cần ghi, d là thời
gian tìm và phát bài đó theo trật tự ghi này
- Dòng thứ n + 1 ghi tổng số thời gian quay băng nếu mỗi bài hát được phát một lần
trong ngày
Với thí dụ trên, kết quả thu được sẽ như sau:
- Cần ghi lần lượt trên băng các bài theo trật tự : bài 2, bài 3, bài 1;
Ta xét vài tình huống ghi băng để rút ra kết luận cần thiết
Trang 2Trật tự ghi trên băng
(x, y, z)
Thời gian phát t(x) + t(y) + t(z); t(i): thời gian tìm và phát bài i ( , , ) (7) + (7 + 2) + (7 + 2 + 3) = 28 phút
Vậy phương án tối ưu sẽ là ( , , ): ghi bài 2 rồi đến bài 3, cuối cùng ghi bài
1 Tổng thời gian theo phương án này là 19 phút
Để có phương án tối ưu ta chỉ cần ghi băng theo trật tự tăng dần của thời lượng Bài toán được cho với giả thiết băng đủ lớn để ghi được toàn bộ các bài Dễ dàng sửa chương trình để vận dụng cho trường hợp dung lượng tăng hạn chế trong M phút Chương trình sắp xếp dữ liệu theo chỉ dẫn
Khoi tri mang chi dan id
quan li sap tang theo chi dan
Trang 3procedure IDQuickSort(d,c: integer);
while a[id[j]] > m do dec(j);
if i <= j then begin
t := 0; {thoi gian tim va phat 1 bai}
tt := 0; {tong thoi gian cho n bai}
Trang 4const string gn = "BangNhac.out";
static public Bang[] b;
static public int n = 0; // so bai hat
static void Main()
int t = 0; // tg tim va phat 1 bai
int tt = 0; // tong tg tim va phat n bai for (int i = 0; i < n; ++i)
// Doc lai file gn de kiem tra ket qua
static void Test() tự viết
static void Doc()
{
int [] a = Array.ConvertAll(
(File.ReadAllText(fn)).Split( new char[] { '\n', ' ', '\t', '\0', '\r'}, StringSplitOptions.RemoveEmptyEntries), new Converter<string, int>(int.Parse));
n = a[0];
Trang 5b = new Bang[n];
for (int i = 1; i <= n; ++i)
b[i-1] = new Bang(a[i],i);
}
public struct Bang
{
public int len;// thoi luong
public int id; // so hieu 1,2,
public Bang(int t, int nn)
t = 0 và chỉ tắt máy sau khi đã hoàn thành đủ N công việc
Dữ liệu vào: tệp văn bản viec.inp:
Ý nghĩa: Cho biết có 4 việc với các thông tin sau:
- Việc thứ nhất phải nộp không muộn hơn thời điểm 1 (giờ) với tiền thưởng 15 (ngàn đồng);
- Việc thứ hai phải nộp không muộn hơn thời điểm 3 (giờ) với tiền thưởng 10 (ngàn đồng);
- Việc thứ ba phải nộp không muộn hơn thời điểm 5 (giờ) với tiền thưởng 100 (ngàn đồng);
- Việc thứ tư phải nộp không muộn hơn thời điểm 1 (giờ) với tiền thưởng 27 (ngàn đồng)
Dữ liệu ra: tệp văn bản viec.out:
- N dòng đầu tiên, dòng thứ t ghi một số tự nhiên i cho biết việc thứ i được làm trong giờ t
- Dòng cuối cùng ghi tổng số tiền thu được
Với thí dụ trên, tệp viec.out sẽ như sau:
Trang 6thưởng 100;
- Giờ thứ 4 thực hiện việc 1;
- Tổng tiền thưởng thu được do đã hoàn thành đúng hạn
ba việc 4, 2 và 3 là 27 + 10 + 100 = 137
Thuật toán
Ta ưu tiên cho những việc có tiền thưởng cao, do đó ta sắp các việc giảm dần theo tiền thưởng Với mỗi việc k ta đã biết thời hạn giao nộp việc đó là h = t[k] Ta xét trục thời gian b Nếu giờ h trên trục đó đã bận do việc khác thì ta tìm từ thời điểm h trở về trước một thời điểm có thể thực hiện được việc k đó Nếu tìm được một thời điểm m như vậy, ta đánh dấu bằng mã số của việc đó trên trục thời gian b, b[m]:= k Sau khi xếp việc xong, có thể trên trục thời gian còn những thời điểm rỗi, ta dồn các việc đã xếp về phía trước nhằm thu được một lịch làm việc trù mật, tức là không có giờ trống Cuối cùng ta xếp tiếp những việc trước đó đã xét nhưng không xếp được Đây là những việc phải làm nhưng không thể nộp đúng hạn nên sẽ không có tiền thưởng Với thí dụ đã cho, N = 4, thời hạn giao nộp t = (1, 3, 5, 1) và tiền thưởng a = (15, 10, 100, 27) ta tính toán như sau:
- Khởi trị: trục thời gian với 5 thời điểm ứng với Tmax = 5 là thờ điểm muôn
nhất phải nộp kết quả, Tmax = max { thời hạn giao nộp }, b = (0, 0, 0, 0,0)
- Chọn việc 3 có tiền thưởng lớn nhất là 100 Xếp việc 3 với thời hạn t[3] = 5 vào
- Chọn nốt việc 2 có tiền thưởng 10 Xếp việc 2 với thời hạn t[2] = 3 vào h:
h[3] = 2
- Ta thu được h = (4, 0, 2, 0, 3)
- Dồn việc trên trục thời gian h, ta thu được h = (4, 2, 3, 0, 0)
- Xếp nốt việc phải làm mà không có thưởng, ta thu được h = (4, 2, 3, 1)
- Ca làm việc kéo dài đúng N = 4 giờ
- Nếu không muốn sắp giảm mảng tiền thưởng a theo chỉ dẫn ta có thể sắp song song a và id như mô tả trong chương trình
Trong chương trình dưới đây ta sử dụng mảng id với hai mục đích: id[i] = v > 0 cho biết việc v đứng thứ i trong dãy được sắp giảm theo giá trị tiền thưởng và việc v chưa được xếp id[i] = v < 0 cho biết việc v đã xếp xong trong lần duyệt đầu tiên
Trang 7gn = 'viec.out'; {output file}
var
a,id,t: array[1 MN] of integer;
{a: tien thuong, t: thoi han giao nop}
{id: chi dan}
h: array[0 MN] of integer; {truc thoi gian}
N: integer; {so luong viec}
f,g: text;
M: integer; {so viec da xep}
tt: longint; {tong so tien thuong}
while a[id[j]] < m do dec(j);
Trang 8{xep duoc viec v tai thoi diem k}
Don cac viec da xep trong h len phia truoc
va tinh tong tien thuong
M := i;
break;
end else tt := tt+a[h[i]];
if M > N then exit;
for i := M+1 to MN do
if h[i] > 0 then begin
Trang 9if id[i] > 0 then begin
Doc; InitID; IDQuickSort(1,n);
XepViec; DonViec; XepTiep; GhiTep;
const string fn = "Viec.inp";
const string gn = "Viec.out";
static public Viec [] v; // cac viec
static public int n = 0; // so luong viec static public int tong = 0;
static public int[] h;
static public int k = 0;
static void Main()
Trang 10int tmax = 0;
for (int i = 0; i < n; ++i)
if (v[i].t > tmax) tmax = v[i].t;
int tt = tmax + n + 1;
h = new int[tt];
// Khoi tri cho h
for (int i = 0; i < tt; ++i) h[i] = 0; tong = 0;
for (int i = 0; i < n; ++i)
// Sap cac viec giam theo tien thuong
static void QSort(int d, int c)
Trang 11if (i <= j)
{
t = v[i]; v[i] = v[j]; v[j] = t; ++i; j;
// Doc lai file gn de kiem tra ket qua
static void Test() tự viết
static void Doc()
{
int [] a = Array.ConvertAll(
(File.ReadAllText(fn)).Split(
new char[] { '\n', ' ', '\t', '\0', '\r' }, StringSplitOptions.RemoveEmptyEntries), new Converter<string, int>(int.Parse));
n = a[0];
v = new Viec[n];
Console.WriteLine(" n = " + n);
int k = 1;
for (int i = 0; i < n; ++i)
v[i] = new Viec(a[k++],a[k++],i+1); }
public struct Viec
{
public int t; // Thoi han giao nop
public int thuong; // Tien thuong
public int id; // Ma so
public Viec(int th, int thg, int nn) { t = th; thuong = thg; id = nn; }
Dữ liệu vào: Tệp văn bản balo.inp:
- Dòng đầu tiên: hai giá trị nguyên dương N và M
- N dòng tiếp theo, mỗi dòng chứa hai giá trị nguyên dương d v cho mỗi vật,
trong đó d là trọng lượng, v là giá trị tính theo một đơn vị trọng lượng của
vật đó (đơn giá) Các số cách nhau qua dấu cách
Trang 123 8
16 6
- Vật thứ ba có trọng lượng 4, đơn giá 2,
- Vật thứ tư có trọng lượng 3, đơn giá 8,
- Vật thứ năm có trọng lượng 16, đơn giá 6
(tr - triệu đồng)
16
172
Dữ liệu ra: Tệp văn bản tên balo.out:
- N dòng, dòng thứ i cho biết trọng lượng cần lấy ở vật thứ i
- Dòng cuối cùng ghi tổng giá trị thu được
Hướng dẫn
Có nhiều bài toán thuộc họ xếp ba lô, thuật toán cho bài này thuộc lớp tham lam
Dễ thấy tiêu chuẩn chọn là giá đơn vị cao Ta duyệt các vật theo giá đơn vị từ cao trở xuống Vật được chọn sẽ được lấy tối đa Như vậy, nếu tổng trọng lượng các vật bằng hoặc lớn hơn sức mang của ba lô thì bao giờ ba lô cũng được xếp đủ
{t: tong trong luong con duoc xep vao balo}
{tt: tong gia tri da lay}
Trang 13while gdv[id[j]] < x do dec(j);
tt := 0; {tong gia tri }
t := m; {trong luong con lai cua balo }
for i :=1 to n do
if t >= a[id[i]] then begin { lay tron vat id[i] }
t := t-a[id[i]];
tt := tt + (a[id[i]]*gdv[id[i]]);
end else { lay cho day balo } begin
tt := tt+(t*gdv[id[i]]); {lay vua du } a[id[i]] := t; {chinh lai vat cuoi }
Trang 14const string fn = "BaLo.inp";
const string gn = "BaLo.out";
static public Item[] Items;
static public int[] t;
static public int n = 0; // so luong vat
static public int m = 0; // suc chua cua Ba lo static public int vh = 0; // Gia tri cua balo static void Main()
// Sap cac BaLo giam theo tien thuong
static public void QSort(int d, int c)
{
int i = d, j = c;
int m = Items[(d + c) / 2].Price;
Item t = new Item(0,0,0);
Trang 15// Doc lai file gn de kiem tra ket qua
static public void Test() tự viết
Items = new Item[n];
for (int k = 2, i = 0; i < n; ++i, k+=2)
Items[i] = new Item(b[k], b[k+1], i); }
public struct Item // mo ta mot mat hang
{
public int Weight; // trong luong
public int Price; // don gia
public int Id; // ma so
public Item(int w, int p, int i)
{ Weight = w; Price = p; Id = i; }
}
} // BaLo
} // SangTao1
Bài 5.4 Cây bao trùm ngắn nhất
Cho một đồ thị liên thông G vô hướng bao gồm n đỉnh, mã số từ 1 đến n, và m
cạnh nối hai đỉnh với nhau Mỗi cạnh có chiều dài cho trước Tính liên thông của
đồ thị cho biết với hai đỉnh cho trước tuỳ ý ta luôn tìm được các cạnh gối đầu
nhau để đi từ đỉnh này đến đỉnh kia Hãy chỉ ra một phần P của đồ thị thoả các
tính chất sau:
Trang 16(i) P chứa tất cả các đỉnh của G;
(ii) P chứa một số ít nhất các cạnh của G;
(iii) P là đồ thị liên thông;
(iv) Tổng chiều dài các cạnh của P là ngắn nhất
Đồ thị P thoả ba tính chất (i), (ii) và (iii) được gọi là cây bao trùm của đồ thị G Nếu P thoả thêm tính chất (iv) thì P được gọi là cây bao trùm ngắn nhất của G Một số tài liệu dùng thuật ngữ cây khung thay cho cây bao trùm và cây khung cực tiểu thay cho
cây bao trùm ngắn nhất
Bài toán trên có nhiều ứng dụng thực tiễn Một trong số ứng dụng đó được mô tả thông qua thí dụ sau:
Có n máy tính được nối với nhau thành mạng bằng cáp quang là một loại dây
truyền tin đắt tiền Trong mạng này, hai máy tính bất kì đều có thể liên lạc được với nhau trực tiếp hoặc thông qua một vài máy trung gian Ta gọi tính chất này là tính liên thông của mạng máy tính Hãy bỏ bớt một số dây nối để n máy tính trên vẫn liên thông được với nhau Mạng tối thiểu thu được chính là một cây bao trùm ngắn nhất của mạng ban đầu
Dữ liệu vào: tệp văn bản tên DOTHI.INP
- Dòng đầu tiên ghi hai số tự nhiên n và m cách nhau qua dấu cách, biểu thị
là (chiều dài mỗi cạnh được ghi sau dấu hai chấm):
cạnh 1 (1, 5): 1 cạnh 2 (4, 8): 1 cạnh 3 (7, 8): 1 cạnh 4 (2, 3): 2 cạnh 5 (1, 6): 2 cạnh 6 (3, 8): 3 cạnh 7 (1, 3): 4 Tổng chiều dài 7 cạnh đã chọn là: 14
Trang 17
Lưu ý rằng đồ thị kết quả thu được ở các bước trung gian có thể không liên thông
mà bao gồm nhiều mảnh liên thông (cây con) Loại đồ thị này được gọi là rừng Kết quả
cuối cùng sẽ là cây vì nó liên thông và được tạo thành từ n - 1 cạnh Ta vận dụng tổ
chức find-union cho các tập đỉnh rời nhau để quản lí các tập đỉnh được chọn nhằm phát
hiện chu trình Cạnh (x, y) khi được ghép vào đồ thị trung gian sẽ tạo thành chu trình khi và chỉ khi các đỉnh x và y cùng nằm trong một cây của đồ thị (rừng) trung gian đó
Như vậy mỗi cây con của đồ thị trung gian được quản lí như một tập con của tập các
đỉnh 1 n của đồ thị ban đầu Tập con này có phần tử đại diện chính là gốc của cây
tương ứng Phần tử này được chọn theo mã số nhỏ nhất Các đỉnh còn lại của cây con đều trỏ đến gốc đó
Dễ thấy cây bao trùm luôn luôn có n đỉnh và n - 1 cạnh
(* Pascal *)
(* -
DOTHI.PAS Cay bao trum ngan nhat
(thuat giai Kruskal)
var
a: array[0 MN] of CANH; { Mang cac canh }
d: array[1 MN] of integer;{dung cho find-union } n: integer; {n: so dinh }
m: integer; {so canh }
(* Sap canh tang theo len *)
procedure qsort(d,c: integer);
Trang 18begin while a[i].len < m do i := i+1;
{Tim dai dien cua tap chua x }
function Find(x: integer): integer;
function Union(x,y: integer): Boolean;
Trang 19const string fn = "DoThi.inp";
const string gn = "DoThi.out";
// do thi co n dinh
// m canh (u,v)
// u la dinh dau, v - dinh cuoi
// Len - chieu dai canh
static int[] d ; // to chuc Find-Union
static int n = 0; // so dinh cua do thi
static int m = 0; // so canh cua do thi
static Canh[] cc; // Tap cac canh
static int [] t; // canh duoc chon
static int k; // so canh duoc chon
static int sum = 0;
static void Main()
// Khoi tri cho Find-Union
for (int i = 1; i <= n; ++i) d[i] = i; for (int i = 0; i < m; ++i)
if (Union(cc[i].U,cc[i].V))
{ t[k++] = i; sum += cc[i].Len; } }
static void Ghi()
{