Professional Documents
Culture Documents
Haiphong - Heap
Haiphong - Heap
1
CHUYÊN ĐỀ: CẤU TRÚC DỮ LIỆU HEAP
I. 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)
II. Các thao tác thường dùng đối với Heap Max
1. 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;
2. 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;
3. 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;
4. 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;
5. 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:
6. 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
end;
5
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;
III. 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 4
1 4 2
1 7 7
1 9
3
1 2
2 1
3
2 4
3
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>
int N, L, NR;
int A[maxn], Heap[maxn], Pos[maxn];
void push(int x)
6
{
int 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;
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;
7
{
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);
}
return 0;
}
8
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] <heap[i div 2]) do
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 <nheap) then inc(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à
N
phi công cao tuổi nhất). HT airline cần chính xác 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ụ.
N
Để 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 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 PILOT.OUT PILOT.INP PILOT.OUT
6 32000 6 33000
10000 7000 5000 3000
9000 3000 4000 1000
6000 4000 9000 7000
5000 1000 11000 5000
9000 3000 7000 3000
8000 6000 8000 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 vào heap, khi i lẻ thì ta lấy trong
heap ra một nút, đây chính là lái phụ.
CONST
tfi = 'pilot.inp';
tfo = 'pilot.out';
max = 100000;
TYPE
11
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 <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);
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.
4. 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 10
15
21
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 = 'pulp.inp';
tfo = 'pulp.out';
max = 100000;
TYPE
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
15
j : longint;
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;
16
end;
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;
17
nhap;
sort(1,n);
xuli;
inkq;
END.
56 đường miễn
89 phí
8 10 – 8 là thượng
10 11 viện
10 12 Chi phí tối thiểu là:
10 13
18
11.
{===============================}
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
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.
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 = 'toycars.inp';
tfo = 'toycars.out';
type
arr1 = array[0..1000000] of longint;
arr2 = array[0..100000] of boolean;
23
var
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);
24
var j:longint;
begin
j:=i*2;
if j>nheap then exit;
if (d[a[j]]<d[a[j+1]])and(j<nheap) then inc(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]]);
25
end;
end;
{------------------------------------------------------------------------}
procedureinkq;
vari,j:longint;
begin
assign(fo,tfo);rewrite(fo);
write(fo,res);
close(fo);
end;
{------------------------------------------------------------------------}
BEGIN
nhap;
khoitao;
xuly;
inkq;
END.
26
Dễ thấy, ta chỉ quan tâm tới ghi nhận kết quả. Tức là nếu
tồn tại cách xây dựng L’, R’ mà
thỏa mãn cũng sử dụng cách xây dựng đó được với các cặp với
, chỉ cần lưu 1 cặp có
Quy hoạch động = cách xếp có với
Nếu mỗi lần ta luôn nắp xâu vào nửa ngắn hơn thì luôn đảm bảo
(do ). Với trạng thái , ta cập nhật cho trạng
thái + độ dài xâu nắp thêm vào. Bài toán trở thành Dijkstra Heap
về trạng thái . Các trạng thái cơ sở có thể là các ký tự 1 của 1 trong 3 xâu ban đầu.
Đáp án bài toán là
{$H+}
uses math ;
const
tfi = 'Base3.inp';
tfo = 'Base3.out';
Nmax = 16001;
vc = 1000000000000000000 ;
type
arr1 = array[0..Nmax] of longint ;
var
fi,fo : text;
n,m,nh,ss,xi,yi : longint;
s : array[1..3] of string ;
len : array[1..3] of longint ;
d : array[-Nmax..Nmax,0..1] of int64 ;
pos : array[-Nmax..Nmax,0..1] of longint;
h,h2 : array[0..4*Nmax] of longint;
procedureNhap;
var
i,j :longint;
begin
ss := 0;
for i := 1 to 3 do
begin
readln(fi,s[i]);
len[i] := length(s[i]);
ss := max(ss,len[i]) ;
end;
end;
procedureUpheap(i:longint) ;
var
x,y:longint ;
begin
x := h[i] ;
27
y := h2[i];
while (i > 1) and (d[x,y] < d[h[i div 2],h2[i div 2]]) do
begin
h[i] := h[i div 2] ;
h2[i] := h2[i div 2];
pos[h[i],h2[i]] := i ;
i := i div 2;
end ;
h[i] := x;
h2[i] := y;
pos[x,y] := i;
end;
proceduredownheap(i:longint);
var
j,x,y:longint;
begin
x := h[i];
y := h2[i];
while i * 2 <= nh do
begin
j := i * 2 ;
if (j <nh) and (d[h[j],h2[j]] >d[h[j+1],h2[j+1]]) then
inc(j);
if d[x,y] <= d[h[j],h2[j]] then break ;
h[i] := h[j];
h2[i] := h2[j];
pos[h[i],h2[i]] := i;
i := j;
end ;
h[i] := x;
h2[i] := y;
pos[x,y] := i ;
end;
procedure Push(i,j:longint);
begin
ifpos[i,j] = 0 then
begin
inc(nh) ;
h[nh] := i;
h2[nh] := j;
pos[i,j] := nh;
Upheap(nh) ;
end else Upheap(pos[i,j]) ;
end ;
procedure Pop;
begin
xi := h[1];
yi := h2[1];
h[1] := h[nh];
h2[1] := h2[nh];
pos[h[1],h2[1]] := 1;
28
dec(nh);
downheap(1) ;
end;
procedure Update ;
var
i,j,k:longint;
begin
for i := 1 to 3 do
begin
if s[i][1] = '0' then k := 0 else k := 1;
j := xi + len[i];
if (j <= ss) and (d[j,k] > d[xi,yi] + len[i]) then
begin
d[j,k] := d[xi,yi] + len[i] ;
push(j,k) ;
end;
j := xi - len[i] ;
if (j >= -ss) and (d[j,yi] > d[xi,yi] + len[i]) then
begin
d[j,yi] := d[xi,yi] + len[i];
push(j,yi) ;
end ;
end;
end ;
procedureinit;
var
i,j,k,u: longint;
begin
for i := -ss to ss do
for j := 0 to 1 do
d[i,j] := vc ;
for i := 1 to 3 do
for j := 1 to len[i] do if s[i][j] = '1' then
begin
if s[i][1] = '0' then k := 0 else k := 1;
u := (j - 1) - (len[i] - j) ;
d[u,k] := len[i] ;
push(u,k) ;
end;
end;
procedurexuly;
var
i,j,k: longint;
begin
whilenh> 0 do
begin
pop;
if (xi = 0) then
begin
write(fo,d[xi,yi]);
exit;
29
end;
update;
end;
write(fo,0) ;
end ;
begin
assign(fi,tfi);reset(fi);
assign(fo,tfo);rewrite(fo);
Nhap;
init;
xuly;
close(fi);
close(fo) ;
end.
IV. Một số bài tập vận dụng
30
2 0.10
11
31
8 9 2 5 0 5 0 0 5 0 2
2 5
1 3 6
1 5 3
1 6 1
2 3 9
5 6 5
6 8 7
3 6 2
4 7 1000
2 8 5
Time limit: 0.2s
10 10 2 ..........
.......... .Iooo.D...
.I....D... ....o.....
.......... ..D.o.D...
..D...D... .*..oo....
.*........ D*...ooooo
D*........ *...D....o
*...D..... ..****...o
..****.... ...Ooooooo
...O...... ..........
..........
32
Time limit: 0.4s
34
V. Tài liệu tham khảo
1. Nguồn bài tập: Thày Nguyễn Thanh Tùng, Thày Lê Minh Hoàng, Rumania OI,
Poland OI, Russian OI, vn.spoj.pl.
2. Giải thuật và lập trình – T.S Lê Minh Hoàng – ĐHSP Hà Nội
3. Website: www.infoarena.ro
4. Test và solution các Thày cô có thể liên hệ với tác giả để download.
35