MỤC LỤC
MỤC LỤC...............................................................................................................................1
Khái niệm................................................................................................................................2
Các thao tác thường dùng đối với Heap Max.........................................................................2
Khai báo..............................................................................................................................2
UpHeap................................................................................................................................2
DownHeap...........................................................................................................................3
Push.....................................................................................................................................4
Pop.......................................................................................................................................4
Dijkstra Heap.......................................................................................................................5
Một số bài tập ứng dụng.........................................................................................................6
1.Bài tập 1: Heapuri............................................................................................................6
2.Bài tập 2: Thả xốp............................................................................................................8
3.Bài tập 3:PILOT.............................................................................................................10
Bài tập4:Tiểu thuyết trinh thám........................................................................................13
Bài tập 5: Cezar ................................................................................................................18
Bài tập 6:Toy Cars............................................................................................................22
Bài tập 7:BASE3 ..............................................................................................................26
Một số bài tập vận dụng........................................................................................................30
Bài tập 1: BOCSOI13.......................................................................................................30
Bài tập 2: Kế hoạch làm bài..............................................................................................31
Bài tập 3: CATUN............................................................................................................31
Bài tập 3: Barbar...............................................................................................................32
Bài tập 4:Cầu cảng............................................................................................................33
4.Bài tập 5:K TỔNG BÉ NHẤT.......................................................................................33
Bài tập 6: BINLADEN......................................................................................................34
Tài liệu tham khảo.................................................................................................................35
1
CHUYÊN ĐỀ: CẤU TRÚC DỮ LIỆU HEAP
Khái niệm
Heap là một trong những cấu trúc dữ liệu đặc biệt quan trọng, nó giúp ta có thể giải
được nhiều bài toán trong thời gian cho phép. Độ phức tạp thông thường khi làm việc với
Heap là O(logN).
Heap thực chất là một cây cân bằng thỏa mãn các điều kiện sau:
• Một nút có không quá 2 nút con.
• Với Heap Max thì nút gốc là nút lớn nhất, mọi nút con đều không lớn hơn nút cha
của nó. Với Heap Min thì ngược lại.
Mặc dù được mô tả như cây nhưng Heap có thể được biểu diễn bằng mảng. Nút con
của nút i là 2*i và 2*i+1. Do Heap là cây cân bằng nên độ cao của 1 nút luôn <= logN.
Ứng dụng chủ yếu của Heap là tìm Min, Max trong 1 tập hợp động (có thể thay đổi,
thêm, bớt các phần tử) nhưng như vậy đã là quá đủ.
(Mô hình biểu diễn Heap bằng cây nhị phân và bằng mảng)
Các thao tác thường dùng đối với Heap Max
Khai báo
Ở ví dụ này, Heap sẽ là mảng một chiều kiểu LongInt, nHeap là số phần tử của mảng.
Const
maxn = 100000;
Var
nHeap : LongInt;
Heap : array[0..maxn] of LongInt;
UpHeap
Nếu 1 nút lớn hơn nút cha của nó thì di chuyển nó lên trên:
2
Procedure UpHeap(i : LongInt);
Begin
if (i = 1) or (Heap[i] < Heap[i div 2]) then exit; // Nếu i là nút gốc hoặc
nhỏ hơn nút cha thì không làm việc
swap(Heap[i] , Heap[i div 2]); // Đổi chỗ 2 phần tử trong Heap;
UpHeap(i div 2); // Tiếp tục di chuyển lên trên
end;
DownHeap
Nếu 1 nút nhỏ hơn nút con thì đẩy nó xuống dưới:
3
Procedure DownHeap(i : LongInt);
Var
j : LongInt;
Begin
j := i*2;
if j > nHeap then exit; // Nếu i không có nút con thì không làm việc
if (j < nHeap) and (Heap[j] < Heap[j+1]) then Inc(j); // Nếu i có 2 nút con thì
chọn nút ưu tiên hơn
if Heap[i] < Heap[j] then // Nếu nút cha nhỏ hơn nút con
begin
swap(Heap[i] , Heap[j]); // Đổi chỗ 2 phần tử trong Heap
DownHeap(j); // Tiếp tục di chuyển xuống dưới
end;
end;
Push
Đưa 1 phần tử mới vào Heap: Thêm 1 nút vào cuối Heap và tiến hành UpHeap từ
đây:
Procedure Push(x : LongInt);
Begin
Inc(nHeap); // Tăng số phần tử của Heap
Heap[nHeap] := x; // Thêm x vào Heap
UpHeap(nHeap);
End;
Pop
Rút ra 1 phần tử ở vị trí v trong Heap: Gán Heap[v] := Heap[nHeap] rồi tiến hành
chỉnh lại Heap:
Function Pop(v : LongInt) : LongInt;
Begin
Pop := Heap[v]; // Lấy phần tử ở vị trí v ra khỏi Heap
Heap[v] := Heap[nHeap]; // Đưa phần tử ở cuối Heap vào vị trí v
Dec(nHeap); // Giảm số phần tử của Heap đi 1
4
{Chỉnh lại Heap}
UpHeap(v);
DownHeap(v);
End;
Ngoài ra, khi sử dụng thuật toán Dijkstra/Prim kết hợp cấu trúc Heap, bạn còn
có thể sử dụng cách Push và Pop khác thuận lợi hơn so với cách trình bày ở trên:
Dijkstra Heap
1/ Update – Dijkstra:
Procedure Update(v : LongInt); // Đỉnh v vừa được sửa nhãn, cần chỉnh lại Heap
var
child , parent : LongInt;
begin
child := pos[v]; // child là vị trí của đỉnh v trong Heap
if child = 0 then // Nếu đỉnh v chưa có trong Heap
begin
Inc(nHeap); // Tăng số phần tử của Heap
child := nHeap; // Đưa v vào cuối Heap
end;
parent := child div 2; // parent là nút cha của child
while (parent > 0) and (d[Heap[parent]] > d[v]) do // Nếu đỉnh ở nút parent
kém ưu tiên hơn v thì bị “kéo xuống” nút child
begin
Heap[child] := Heap[parent]; // Đẩy đỉnh được lưu trong nút cha xuống nút con
pos[Heap[child]] := child; // Ghi nhận lại vị trí mới của đỉnh đó
child := parent; // Tiếp tục di chuyển lên
parent := child div 2;
end;
Heap[child] := v; // Thao tác “kéo xuống” ở trên sẽ tạo ra 1 ô trống ở nút child để đặt v vào
pos[v] := child; // Ghi nhận vị trí mới của đỉnh v trong Heap
end;
2/ Pop – Dijkstra:
Function Pop : LongInt; // Lấy từ Heap đỉnh có nhãn tự do nhỏ nhất
var
r , v , c : LongInt;
begin
Pop := Heap[1]; // Nút gốc là nút có nhãn tự do nhỏ nhất
v := Heap[nHeap]; // v là đỉnh ở nút lá cuối Heap, sẽ được đảo lên đầu và vun đống
Dec(nHeap); // Giảm số phần tử của Heap
r := 1; // Bắt đầu từ nút gốc
while r*2 <= nHeap do // Chừng nào r chưa phải là lá
begin
c := r*2; // c là nút con của r
if (c <nHeap) and (d[Heap[c]] > d[Heap[c+1]]) then Inc(c); // Trong
2 nút con chọn nút con chứa đỉnh ưu tiên hơn
if d[Heap[c]] >= d[v] then break; // Nếu v ưu tiên hơn thì không làm việc nữa
Heap[r] := Heap[c]; // Chuyển đỉnh lưu ở nút con lên nút cha
pos[Heap[r]] := r; // Cập nhật lại vị trí mới trong Heap của đỉnh đó
r := c; // Tiếp tục di chuyển xuống dưới
5
end;
Heap[r] := v; // Đỉnh v được đặt vào vị trí r để đảm bảo cấu trúc Heap
pos[v] := r; // Ghi nhận vị trí mới của đỉnh v trong Heap
end;
Một số bài tập ứng dụng
1. Bài tập 1: Heapuri
Cho danh sách gồm N phần tử. Chúng ta thực hiện một số thao tác trên danh sách. Có 3
loại thao tác sau :
- Thao tác 1 : Cho phần tử x vào danh sách
- Thao tác 2 : Xóa phần tử thứ t (theo thứ tự nhập vào)
- Thao tác 3: Lấy ra phần tử nhỏ nhất trong danh sách
Yêu cầu: Hãy cho biết các kết quả của thao tác 3 ?
INPUT: HEAPURI.INP
-
Dòng 1: N
-
Các dòng tiếp theo là dãy thao tác cần xử lý theo định dạng 1 x, 2 x hoặc 3 tương
ứng là thao tác 1, 2 và 3
OUTPUT : HEAPURI.OUT
- Ghi trên nhiều dòng, mỗi dòng là kết quả của thao tác 3 theo thứ tự trong file input.
Ví dụ :
HEAPURI.INP
HEAPURI.OUT
9
1
1
1
3
1
2
3
2
3
4
7
9
4
2
7
2
1
4
Dễ thấy đây là một danh sách động (số lượng phần tử thay đổi), vì vậy ta xây dựng một
heapmin để lấy ra giá trị nhỏ nhất ở các thao tác 3. Để xóa phần tử thứ t theo thứ tự nhập
vào thì ta lưu thêm một mảng Pos.
#include <stdio.h>
#include <assert.h>
#define maxn 200010
int N, L, NR;
int A[maxn], Heap[maxn], Pos[maxn];
6
void push(int x)
{
int aux;
while (x/2 && A[Heap[x]]
{
aux = Heap[x];
Heap[x] = Heap[x/2];
Heap[x/2] = aux;
Pos[Heap[x]] = x;
Pos[Heap[x/2]] = x/2;
x /= 2;
}
}
void pop(int x)
{
int aux, y = 0;
while (x != y)
{
y = x;
if (y*2<=L && A[Heap[x]]>A[Heap[y*2]]) x = y*2;
if (y*2+1<=L && A[Heap[x]]>A[Heap[y*2+1]]) x = y*2+1;
aux = Heap[x];
Heap[x] = Heap[y];
Heap[y] = aux;
Pos[Heap[x]] = x;
Pos[Heap[y]] = y;
}
}
intmain()
{
freopen("heapuri.in", "r", stdin);
freopen("heapuri.out", "w", stdout);
int i, x, cod;
scanf("%d ", &N);
assert(1<=N && N<=200000);
for (i=1; i<=N; i++)
{
scanf("%d ", &cod);
assert(1<=cod && cod<=3);
7
if (cod < 3)
{
scanf("%d ", &x);
assert(1<=x && x<=1000000000);
}
if (cod == 1)
{
L++, NR++;
A[NR] = x;
Heap[L] = NR;
Pos[NR] = L;
push(L);
}
if (cod == 2)
{
A[x] = -1;
assert(Pos[x] != 0);
assert(1<=x && x<=NR);
push(Pos[x]);
Pos[Heap[1]] = 0;
Heap[1] = Heap[L--];
Pos[Heap[1]] = 1;
if (L>1) pop(1);
}
if (cod == 3) printf("%d\n", A[Heap[1]]);
}
return 0;
}
2. Bài tập 2: Thả xốp
Có N hạt xốp, hạt thứ i có khối lượng
, được thả lần lượt xuống một ống nước đặc
biệt được thiết kế sao cho tại mỗi thời điểm chỉ có một hạt xốp nhẹ nhất nổi lên trên bề
mặt. Trước mỗi lần thả, hạt xốp đang nổi trên bề mặt sẽ bị ngấm nước và tăng gấp đôi
khối lượng. Hỏi sau khi thả hạt xốp cuối cùng vào ống thì khối lượng xốp tăng so với tổng
khối lượng ban đầu là bao nhiêu ?
INPUT:SPONGE.INP
-
Dòng 1: Số nguyên dương N
-
Dòng 2: N số nguyên dương
OUTPUT :SPONGE.OUT
- Ghi 1 số duy nhất là đáp án của bài toán
8
Ví dụ:
SPONGE.INP
SPONGE.OUT
3
3
2 1 3
Tương tự bài 1, dễ thấy đây là một danh sách động (giá trị phần tử thay đổi), vì vậy ta xây
dựng một heapmin để lấy ra hạt xốp nhỏ nhất, tăng gấp đôi khối lượng rồi cập nhật lại
heapmin.
COnst
tfi='sponge.in';
tfo='sponge.out';
Type
arr1=array[0..100001] of longint;
Var
fi,fo
: text;
w,heap,head,ke,d,c,dd,cost
:
arr1;
n,nheap,delta
: longint;
{==================================}
Procedure nhap;
Var
i :longint;
Begin
Assign(fi,tfi);Reset(fi);
read(fi,n);
For i := 1 to n do
read(fi,w[i]);
cost := w;
Close(fi);
End;
{==================================}
Procedure doicho(varx,y : longint);
var
tmp :longint;
Begin
tmp := x;
x
:= y;
y
:= tmp;
End;
{==================================}
Procedure upheap(i : longint);
Var
j :longint;
Begin
While (i <> 1) and (heap[i]
Begin
doicho(heap[i],heap[i div 2]);
i := i div 2;
End;
End;
9
{=============================}
Procedure downheap(i : longint);
Var
j :longint;
Begin
While (2*i <= nheap) do
Begin
j := 2*i;
If (heap[j] >heap[j+1]) and (j
If heap[i] > heap[j] then
Begin
doicho(heap[i],heap[j]);
i :=j;
End
else exit;
End;
End;
{=============================}
Procedure push(x :longint);
Begin
inc(nheap);
heap[nheap] := x;
upheap(nheap);
End;
{=============================}
Procedure xuly;
Var
i1 :longint;
Begin
For i1 := 1 to n-1 do
Begin
push(w[i1]);
delta := delta + heap[1];
heap[1] := heap[1]*2;
downheap(1);
End;
End;
{==================================}
BEGIN
Assign(fo,tfo);Rewrite(fo);
nhap;
xuly;
write(fo,delta);
Close(fo);
END.
3. Bài tập 3:PILOT
HT AIRLINE là một hãng hàng không danh tiếng ở Việt Nam, tuy nhiên, để tồn tại
trong cơn bão suy thoái kinh tế, Ban giám đốc quyết định giảm chi phi tiền lương cho phi
công càng nhiều càng tốt.
10
HT airline có tất cả N phi công (N là số chẵn), các phi công được đánh số từ 1 đến N
(Phi công 1 là phi công trẻ nhất, phi công i là phi công có tuổi cao thứ i,… phi công n là
phi công cao tuổi nhất). HT airline cần chính xác
N
phi hành đoàn, mỗi phi hành đoàn
2
gồm 2 phi công (một lái chính và một lái phụ), lái chính phải nhiều tuổi hơn lái phụ.
Hợp đồng mà công ty kí với các phi công có 2 điều khoản rõ ràng: tiền lương khi là lái
chính và tiền lương khi là lái phụ. Rõ ràng, đối với 1 phi công, tiền lương lái chính bao giờ
cũng cao hơn tiền lương khi lái phụ. Tuy nhiên, với một phi hành đoàn, có thể tiền lương
của lái chính lại thấp hơn lái phụ.
Để giảm chi phí trả tiền lương, HT phải xác định một cách phân chia tối ưu
N
phi
2
hành đoàn.
Bạn hãy giúp HT viết chương trình xác định số tiền tối thiểu để trả lương cho N phi
công.
INPUT: PILOT.INP
- Dòng 1 : Số nguyên dương N, là số phi công ở HT airline.(2 ≤ N ≤ 10000; N là số
chẵn)
- N dòng tiếp theo, dòng thứ i là thông tin về phi công i : gồm hai số a và c viết cách
nhau 1 dấu cách trống, tương ứng là tiền lương khi lái chính và tiền lương khi lái
phụ (1 ≤ a < c ≤ 100.000).
OUTPUT: PILOT.OUT
- Một số nguyên duy nhất là tiền lương tối thiểu phải trả cho N phi công.
Ví dụ :
PILOT.INP
6
10000
9000
6000
5000
9000
8000
PILOT.OUT
32000
7000
3000
4000
1000
3000
6000
PILOT.INP
6
5000
4000
9000
11000
7000
8000
PILOT.OUT
33000
3000
1000
7000
5000
3000
6000
Time limits / test: 1s
Ta có nhận xét sau: Theo thứ tự i nhập vào từ 1 đến N, thì cứ i lẻ thì phải có 1 lái phụ. Vì
vậy, ta xây dựng một heap max, lần lượt cho
heap ra một nút, đây chính là lái phụ.
CONST
tfi
tfo
max
TYPE
=
=
=
'pilot.inp';
'pilot.out';
100000;
11
vào heap, khi i lẻ thì ta lấy trong
Arr1 = array[1..max] of longint;
VAR
fi,fo
:
text;
n,nh,sum,kq
:
longint;
a,b,h
:
Arr1;
{*-----------------------------------------------------------------*}
procedurenhap;
var
i
:
longint;
begin
assign(fi,tfi);reset(fi);
read(fi,n);
For i := 1 to n do
read(fi,a[i],b[i]);
close(fi);
end;
{*-----------------------------------------------------------------*}
proceduredoicho(varx,y : longint);
var
tg
:
longint;
begin
tg := x;
x := y;
y := tg;
end;
{*-----------------------------------------------------------------*}
procedureupheap(i : longint);
begin
if (i = 1) or (h[i] < h[i div 2]) then exit;
if h[i div 2] < h[i] then
begin
doicho(h[i div 2],h[i]);
upheap(i div 2);
end;
end;
{*-----------------------------------------------------------------*}
proceduredownheap(i : longint);
var
j
:
longint;
begin
j := 2 *i;
if j >nh then exit;
if (j
if h[i] < h[j] then
begin
doicho(h[i],h[j]);
downheap(j);
end;
end;
{*-----------------------------------------------------------------*}
procedure push(x : longint);
12
begin
inc(nh);
h[nh] := x;
upheap(nh);
end;
{*-----------------------------------------------------------------*}
function pop : longint;
begin
pop := h[1];
h[1] := h[nh];
dec(nh);
downheap(1);
end;
{*-----------------------------------------------------------------*}
procedurexuli;
var
i
:
longint;
begin
sum := 0;nh := 0;kq := 0;
For i := 1 to n do
begin
sum := sum + a[i];
push(a[i] - b[i]);
if i mod 2 = 1 then kq := kq + pop;
end;
end;
{*-----------------------------------------------------------------*}
procedureinkq;
begin
assign(fo,tfo);rewrite(fo);
write(fo,sum-kq);
close(fo);
end;
{*-----------------------------------------------------------------*}
{*-----------------------------------------------------------------*}
{*-----------------------------------------------------------------*}
BEGIN
nhap;
xuli;
inkq;
END.
Bài tập4:Tiểu thuyết trinh thám
Ivan Đneprôp viết truyện tiểu thuyết trinh thám. Truyện của anh ta không có gì đặc
sắc: không có các tình huống ly kỳ đặc biệt, cũng không có các chi tiết hài hước tế nhị.
Thậm chí một hiệu sách đã bán các sáng tác của Ivan theo cân! Nhưng độc giả lại thích
truyện của Ivan. Nó dễ hiểu và giúp người ta thư giản sau một ngày lao động mệt nhọc.
13
Thực ra, bí mật sự thành công của Ivan là ở chổ không phải chính anh nghĩ ra các tình
huống mà là người em trai Alexei. Ivan có nhiệm vụ viết nó thành các bestsellers. Dĩ
nhiên hai anh em chia nhau hợp lý số tiền kiếm được. Điều đáng tiếc là khả năng sáng tạo
các tình huống ly kỳ của Alexei lại phụ thuộc vào chu kỳ sinh học của anh. Hai anh em
phân tích bảng chu kỳ sinh học của Alexei và thấy rằng trong thời gian tới Alexei sẽ nghĩ
được n chủ đề mới, chủ đề thứ i sẽ được nghĩ ra ở ngày ri. Trong cùng một ngày có thể
Alexei nghĩ ra tới vài câu chuyện.
Với mỗi chủ đề, Ivan thời lượng cần thiết để hoàn thành tác phẩm và tính được rằng
chủ đề thứ i cần có pi ngày để viết. Ivan có trí nhớ cực tốt, vì vậy anh có thể tạm bỏ dở
một truyện, chuyển sang viết truyện khác sau đó quay lại hoàn thành nốt các truyện dở
dang.
Dĩ nhiên, hai anh em muốn sách được viết càng nhanh càng tốt, tức là phải cực tiểu
hóa thời gian trung bình từ thời điểm hiện tại tới thời điểm lúc tiểu thuyết hoàn thành. Vì
số sách là cố định, nên điều này tương đương với việc cực tiểu hóa tổng thời gian viết tất
cả các cuốn sách. Điều này có nghĩa là nếu cuốn sách thứ i được hoàn thành vào ngày ci
thì
∑c
i
phải được cực tiểu hóa. Ví dụ, ở ngày thứ nhất Alexei nghĩ ra một cốt chuyện mà
Ivan cần viết trong 5 ngày, Ivan bắt tay viết ngay. Ngày thứ 2 Alexei nghĩ thêm một cót
chuyện mới cần viết trong một ngày. Ivan chuyển sang viết chuyện mới, ngày thứ 3:
chuện thứ hai hoàn thành và Ivan quay lại viết tiếp chuyện thứ nhất, mất thêm 4 ngày nữa,
đến ngày thứ 7 chuyện thứ nhất hoàn thành. Tổng các thời điểm hoàn thành là 3+7 = 10.
Yêucầu: Cho n, ri và pi. Hãy xác định tổng thời gian ngắn nhất hoàn thành tất cả các
truyện.
INPUT:PULP.INP:
- Dòng 1: Chứa số nguyên n (1 ≤ n ≤ 100.000)
- Mỗi dòng trong n dòng sau chứa 2 số nguyên ri và pi.
OUTPUT: PULP.OUT
- Một số nguyên – tổng thời gian ngắn nhất tìm được.
Ví dụ:
PULP.INP
PULP.OUT
2
15
21
10
Thuật toán:
1. Sort tăng theo R[i]
2. Ví dụ với các thời điểm
, dễ dàng ta thấy
3. Với mỗi khoảng thời gian t = R[i] – R[i-1], ta ưu tiên giải quyết công việc cần ít
thời gian nhất, giả sử là công việc P (Thấy ngay là sử dụng Heap để lấy công việc
có thời gian nhỏ nhất)
a. Nếu thời gian thực hiện công việc P < t thì thực hiện hết công việc P, thời
gian còn lại thực hiện tiếp các công việc có thời gian nhỏ tiếp theo.
14
b. Nếu thời gian thực hiện công việc P > t thì chúng ta thực hiện công việc P
trong khoảng thời gian t, thời gian còn lại là t[P] – t ta lưu lại trong Heap để
tính tiếp.
4. Sau khi xét đến thời điểm N, lúc này ta sẽ phải làm tất cả các công việc còn lại tuần
tự từ nhỏ đến lớn, hay là lấy tất cả các phần tử trong Heap ra là xong.
CONST
tfi
tfo
max
TYPE
=
=
=
'pulp.inp';
'pulp.out';
100000;
Arr1 = array[1..max] of longint;
VAR
fi,fo
:
text;
n,nh,sum,res :
longint;
r,p,h
:
Arr1;
{*------------------------------------------------------------*}
procedurenhap;
var
i
:
longint;
begin
assign(fi,tfi);reset(fi);
read(fi,n);
For i := 1 to n do
read(fi,r[i],p[i]);
close(fi);
end;
{*------------------------------------------------------------*}
proceduredoicho(varx,y : longint);
var
tg
:
longint;
begin
tg := x;
x := y;
y := tg;
end;
{*------------------------------------------------------------*}
procedureupheap(i : longint);
begin
if (i = 1) or (h[i] > h[i div 2]) then exit;
if h[i div 2] > h[i] then
begin
doicho(h[i div 2],h[i]);
upheap(i div 2);
end;
end;
{*------------------------------------------------------------*}
proceduredownheap(i : longint);
var
j
:
longint;
15
begin
j := 2 * i;
if j >nh then exit;
if (j <nh) and (h[j] > h[j+1]) then inc(j);
if h[i] > h[j] then
begin
doicho(h[i],h[j]);
downheap(j);
end;
end;
{*------------------------------------------------------------*}
procedure push(x : longint);
begin
inc(nh);
h[nh] := x;
upheap(nh);
end;
{*------------------------------------------------------------*}
function pop
:
longint;
begin
pop := h[1];
h[1] := h[nh];
dec(nh);
downheap(1);
end;
{*------------------------------------------------------------*}
procedurexuli;
var
i,t,x
:
longint;
begin
nh := 0;
push(p[1]);
sum := r[1];
res := 0;
For i := 2 to n do
begin
t := r[i] - r[i-1];
while (nh> 0) and (t > 0) do
begin
x := pop;
if (x <= t) then
begin
t := t - x;
sum := sum + x;
res := res + sum;
end
else
begin
push(x-t);
t := 0;
end;
16
end;
push(p[i]);sum := r[i];
end;
whilenh> 0 do
begin
x := pop;
sum := sum + x;
res := res + sum;
end;
end;
{*------------------------------------------------------------*}
procedure sort(x,y:longint);
var
i,j,key1,key2
:
longint;
begin
i := x;
j := y;
key1 := r[x+random(y-x+1)];
key2 := p[x+random(y-x+1)];
repeat
while (r[i] < key1) or ((r[i] = key1) and (p[i] < key2)) do inc(i);
while (r[j] > key1) or ((r[j] = key1) and (p[j] > key2)) dodec(j);
if i <= j then
begin
doicho(r[i],r[j]);
doicho(p[i],p[j]);
inc(i);
dec(j);
end;
until i > j ;
if x < j then sort(x,j);
if y > i then sort(i,y);
end;
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
procedureinkq;
begin
assign(fo,tfo);rewrite(fo);
write(fo,res);
close(fo);
end;
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
{*------------------------------------------------------------*}
BEGIN
randomize;
nhap;
17
sort(1,n);
xuli;
inkq;
END.
Bài tập 5: Cezar
Tại HT Land, có tất cả n thượng nghị sĩ ở trong n ngôi biệt thự (đánh số từ 1 đến n),
giữa 2 ngôi nhà có một đường đi duy nhất: đường đi trực tiếp hoặc không trực tiếp (đi qua
các con đường khác). Tất cả các ngôi nhà đều đi được đến nhau.
Mỗi thượng nghị sĩ khi đi từ nhà mình đến thượng viện, phải trả 1 USD khi đi qua
một con phố (phố = đường nối trực tiếp 2 nhà bất kỳ). HT – người đứng đầu thượng viện đã nghĩ cách làm sao cho số tiền mà các thượng nghĩ sĩ phải trả là tối thiểu. Vì vậy, HT
quyết định
• Có k con phố miễn phí (thượng nghị sĩ sẽ không phải trả tiền khi đi trên con
phố này)
• Đặt tòa nhà thượng viện ở một trong n ngôi nhà
Bạn hãy viết chương trình tính xem chi phí tối thiểu là bao nhiêu?
INPUT: CEZAR.INP
- Dòng 1: Số nguyên n và k tương ứng là số ngôi nhà ở HT land và số đường phố
miễn phí
- N – 1 dòng tiếp theo, mỗi dòng chứa 2 số i j có nghĩa là có con phố nối ngôi nhà i
và ngôi nhà j
OUTPUT: CEZAR.OUT
Chi phí tối thiểu phải trả
Giới hạn:
•
Ví dụ
CEZAR.INP
13 3
12
23
28
78
75
54
56
89
8 10
10 11
10 12
10 13
CEZAR.OUT
11
Giải thích
Có nhiều phương án
giải quyết: Đây là 1
phương án:
– 5-7, 7-8, 8-10
là
những
đường
miễn
phí
– 8 là thượng
viện
Chi phí tối thiểu là:
11.
18
6
3
1
4
2
5
7
8
10
9
11
12
13
Time limit:0.5 s/test
Thuật toán:
1. Chúng ta sẽ tìm
con đường phải trả phí đi lại.
2. Khởi tạo D[i] = trọng số của đỉnh i với ý nghĩa = số lượng người đi đến thượng
viện phải đi qua con đường này. Ban đầu
3. Tại mỗi bước chúng ta sẽ cho các nút lá vào Heap, mỗi lần lấy 1 phần tử trong heap
chúng ta sẽ update lại trọng số của đỉnh kề với đỉnh vừa lấy ra và nếu đỉnh vừa
update thành nút lá ta lại cho vào trong heap. Sau khi lấy xong
nhật đáp án.
Const
tfi='cezar.inp';
tfo='cezar.out';
oo = 1000000000;
Type
arr1=array[0..100000] of longint;
arr2=array[0..100000] of boolean;
arr3=array[0..15000] of longint;
arr4=array[0..15000] of arr3;
Var
fi,fo
: text;
heap,head,ke,d,c,pos,cost,trace,deg : arr1;
free
: arr2;
k,n,nheap,f,r,sum,res
: longint;
count : arr4;
{===============================}
Procedure nhap;
Var
i,u,v :
longint;
Begin
Assign(fi,tfi);Reset(fi);
read(fi,n,k);
For i := 1 to n-1 do
Begin
read(fi,u,v);
d[i] := u;
c[i] := v;
inc(deg[u]);
inc(deg[v]);
ENd;
head[1] := deg[1];
For i := 2 to n+1 do
Begin
head[i] := head[i-1] + deg[i];
19
đỉnh thì cập
End;
For i := 1 to n-1 do
Begin
u := d[i];
v := c[i];
ke[head[u]] := v;
ke[head[v]] := u;
cost[head[u]] := 1;
cost[head[v]] := 1;
dec(head[u]);
dec(head[v]);
End;
Close(fi);
End;
{===============================}
Procedure khoitao;
Var
i :longint;
Begin
Fillchar(free,sizeof(free),true);
For i := 1 to n do
BEgin
pos[i] := 0;
cost[i] := 1;
End;
nheap := 0;
End;
{===============================}
Procedure downheap(root : longint);
Var
child,u : longint;
BEgin
u := heap[root];
repeat
child := root*2;
If (child <nheap) and (cost[heap[child]] > cost[heap[child+1]])
then inc(child);
If (child >nheap) or (cost[heap[child]]>= cost[u]) then break;
heap[root] := heap[child];
pos[heap[root]] := root;
root := child;
Until false;
heap[root] := u;
pos[u] := root;
End;
{===============================}
Procedure upheap(child : longint);
Var
root,u : longint;
Begin
u := heap[child];
20
Repeat
root := child div 2;
If (root=0) or (cost[heap[root]] <= cost[u]) then break;
heap[child] := heap[root];
pos[heap[child]] := child;
child := root;
Until false;
heap[child] := u;
pos[u] := child;
End;
{===============================}
FUnctionpop :longint;
Begin
pop := heap[1];
heap[1] := heap[nheap];
pos[heap[1]] := 1;
dec(nheap);
downheap(1);
End;
{===============================}
Procedure push(x :longint);
Begin
inc(nheap);
heap[nheap] := x;
pos[heap[nheap]] := nheap;
upheap(nheap);
End;
{===============================}
Procedure update(v : longint);
Var
i :longint;
Begin
For i := head[v] + 1 to head[v+1] do
Begin
dec(deg[ke[i]]);
cost[ke[i]] := cost[ke[i]] + cost[v];
If deg[ke[i]] = 1 then push(ke[i]);
End;
End;
{===============================}
Function dijkstra(x :longint) : longint ;
Var
i,u : longint;
Begin
push(x);
repeat
u := pop;
free[u] := false;
update(u);
untilnheap = 0;
End;
21
{===============================}
Procedure xuly;
Var
i,u : longint;
Begin
khoitao;
res := 0;
For i := 1 to n do
Begin
If deg[i]=1 then push(i);
End;
repeat
u := pop;
sum := sum + cost[u];
inc(res);
If res=n-1-k then break;
free[u] := false;
update(u);
until false;
End;
{===============================}
Procedure inkq;
Begin
Assign(fo,tfo);REwrite(fo);
write(fo,sum);
Close(fo);
End;
{===============================}
BEGIN
nhap;
xuly;
inkq;
END.
Bài tập 6:Toy Cars
Bé Tom là một đứa trẻ ba tuổi và rất thích chơi đồ chơi oto. Bé Tom có n chiếc oto
khác nhau, chúng được đặt trên một chiếu giá cao mà Tom không thể tự mình lấy được.
Phòng của Tom cũng rất nhỏ, tại một thời điểm, không thể có nhiều hơn k chiếc oto đồ
chơi ở trên sàn nhà.
Tom chơi với một trong nhưng chiếc oto trên sàn nhà, Mẹ của Ton luôn ở trong
phòng với Tom trong cả thời gian chơi của Tom. Khi Bé Tom muốn chơi với một chiếc oto
khác, nếu chiếc này ở trên sàn nhà, Tom sẽ tự lấy để chơi, còn nếu chiếc oto này ở trên
giá, Mẹ của Tom sẽ lấy xuống cho Tom. (Khi Mẹ của Tom lấy 1 chiếc oto cho Tom,cùng
lúc cô ấy có thể lấy một chiếc oto bất kỳ khác ở sàn nhà để đặt lên giá – để có đủ khoảng
không gian cho k chiếc oto)
22
Mẹ của Tom là người mẹ rất hiểu ý thích của con mình, cô ta có thể biết được
những chiếc oto nào mà con trai mình muốn chơi. Cô ta muốn biết số lần ít nhất mà cô ta
giúp Tom lấy xe oto từ trên giá.
INPUT: TOYCARS.INP
-
Dòng 1: 3 số nguyên dương N, K, P
lần lượt là số lượng oto mà Tom có, số lượng oto có thể đặt trên sàn tại cùng một
thời điểm và độ dài dãy các oto mà Tom muốn chơi. Các oto được đánh số từ 1 đến
N.
- P dòng tiếp theo, mỗi dòng 1 số nguyên dương là chiếc oto mà Tom muốn chơi
(theo thứ tự thời gian)
OUTPUT: TOYCARS.OUT
- Một số nguyên duy nhất là số lần ít nhất Mẹ Tom lấy oto từ trên giá xuống.
Ví dụ:
TOYCARS.INP
TOYCARS.OUT
327
4
1
2
3
1
3
1
2
Thuật toán :
1. Gọi Next[i] = thời điểm gần nhất sau thời điểm i mà bé Tom muốn chơi món đồ
chơi P[i]. Next[i] =
nếu i là thời điểm cuối cùng bé Tom muốn chơi món P[i].
Như vậy có thể coi Next[i] = độ tồi của việc giữ lại món đồ chơi P[i] ở trên sàn
(Next[i] càng lớn tức là việc giữ lại P[i] càng vô ích).
2. Xét lần lượt các thời điểm i :
a. Nếu món P[i] đang nằm trên sàn : bỏ qua và xét thời điểm tiếp theo.
b. Nếu mon P[i] đang nằm trên giá :
i. Nếu sàn chưa đầy : Ta lấy món P[i] để lên sàn.
ii. Nếu sàn đầy : Ta chọn từ sàn một món đồ chơi có độ tồi lớn nhất và
đặt lên giá, sau đó lấy món P[i] để lên sàn.
3. Dễ dàng nhận thấy có thể sử dụng cấu trúc Heap để thực hiện thuật giải trong thời
gian N.logK.
const
tfi
tfo
type
=
=
'toycars.inp';
'toycars.out';
arr1
arr2
=
=
array[0..1000000] of longint;
array[0..100000] of boolean;
var
23
fi,fo:text;
n,k,p,res,nheap,x,dem,vc:longint;
c,deg,d,a,pos,b,vt,next:arr1;
free:arr2;
{------------------------------------------------------------------------}
procedurenhap;
vari,j:longint;
begin
assign(fi,tfi);reset(fi);
read(fi,n,k,p);
for i:=1 to p do
read(fi,c[i]);
close(fi);
end;
{------------------------------------------------------------------------}
procedurekhoitao;
vari,j:longint;
begin
res:=0;
for i:=1 to n do
begin
free[i]:=true;
vt[i]:=p+1;
end;
for i:=p downto 1 do
begin
b[i]:=vt[c[i]];
vt[c[i]]:=i;
end;
end;
{------------------------------------------------------------------------}
proceduredoicho(vari,j:longint);
vartg:longint;
begin
tg:=i;
i:=j;
j:=tg;
end;
{-----------------------------------------------------------------------}
procedureupheap(i:longint);
varj,k:longint;
begin
if (i=1)or(d[a[i]]<=d[a[i div 2]]) then exit;
j:=i div 2;
doicho(a[i],a[j]);
doicho(pos[a[i]],pos[a[j]]);
upheap(j);
end;
{-----------------------------------------------------------------------}
proceduredownheap(i:longint);
var j:longint;
24
begin
j:=i*2;
if j>nheap then exit;
if (d[a[j]]
if d[a[j]]>d[a[i]] then
begin
doicho(a[i],a[j]);
doicho(pos[a[i]],pos[a[j]]);
downheap(j);
end;
end;
{-----------------------------------------------------------------------}
procedure push(i,j:longint);
begin
inc(nheap);
a[nheap]:=i;
pos[i]:=nheap;
d[i]:=b[j];
upheap(nheap);
end;
{-----------------------------------------------------------------------}
procedure pop;
vari,j:longint;
begin
x:=a[1];
a[1]:=a[nheap];
pos[a[nheap]]:=1;
dec(nheap);
downheap(1);
end;
{-----------------------------------------------------------------------}
procedurexuly;
vari,j:longint;
begin
for i:=1 to p do
if free[c[i]] then
begin
ifnheap=k then
begin
pop;
free[x]:=true;
end;
push(c[i],i);
free[c[i]]:=false;
inc(res);
end
else
begin
d[c[i]]:=b[i];
upheap(pos[c[i]]);
end;
25