1. Trang chủ
  2. » Giáo Dục - Đào Tạo

CHUYÊN ĐỀ BỒI DƯỠNG HSG TIN HỌC MỘT SỐ CẤU TRÚC DỮ LIỆU ĐẶC BIỆT

48 237 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 48
Dung lượng 396 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Là tài liệu được trình bày từ cơ bản đến nâng cao về ứng dụng các cấu trúc dữ liệu đặc biệt trong việc giải các bài toán thi học sinh giỏi Tin học. Có các bài tập minh họa và hướng dẫn cách giải từ đề thi HSG tỉnh, Quốc gia các năm

Trang 1

CHUYÊN ĐỀ 6 MỘT SỐ CẤU TRÚC DỮ LIỆU

Việc cài đặt một danh sách trong máy tính tức là tìm một cấu trúc dữ liệu cụ thể mà máytính hiểu được để lưu các phần tử của danh sách đồng thời viết các đoạn chương trình con

mô tả các thao tác cần thiết đối với danh sách

Vì danh sách là một tập sắp thứ tự các phần tử cùng kiểu, ta ký hiệu TElement là kiểu dữliệu của các phần tử trong danh sách, khi cài đặt cụ thể, TElement có thể là bất cứ kiểu dữliệu nào được chương trình dịch chấp nhận (Số nguyên, số thực, )

procedure Insert(p: Integer;const v: TElement);

Trang 2

procedure Delete(p: Integer);

Trong trường hợp cần xóa một phần tử mà không cần duy trì thứ tự của các phần tử khác,

ta chỉ cần đưa giá trị phần tử cuối cùng vào vị trí cần xóa rồi giảm số phần tử của mảngxuống 1 Khi đó thời gian thực hiện của phép xóa chỉ là 0(l)

2 Danh sách liên kết

Danh sách nối đơn (Singly-linked list) gồm các nút được nối với nhau theo một chiều.Mỗi nút là một bản ghi (record) gồm hai trường:

- Trường in/o chứa giá trị lưu trong nút đó

- Trường link chứa liên kết (con trỏ) tới nút kế tiếp, tức là chứa một thông tin đủ để biếtnút kế tiếp nút đó trong danh sách là nút nào, trong trường hợp là nút cuối cùng (không cónút kế tiếp), trường liên kết này được gán một giá trị đặc biệt, chẳng hạn con trỏ nil.Type PNode = ^TNode ; //Kiểu con trỏ tới một nút

TNode = record; //Kiểu biến động chứa thông tin trong một nút

info: TElement;

link: PNode;

end;

Để duyệt danh sách nối đơn, ta bắt đầu từ nút đầu tiên (head), dựa vào trường liên kết để

đi sang nút kế tiếp, đến khi gặp giá trị đặc biệt (duyệt qua nút cuối) thì dừng

a Truy cập phần tử trong danh sách nối đơn

Việc xác định phần tử đứng thứ p trong danh sách bắt buộc phải duyệt từ đầu danh sáchqua p nút, việc này mất thời gian trung bình 0(n)

b Chèn phần tử vào danh sách nối đơn

Để chèn thêm một nút chứa giá trị v vào vị trí của nút p trong danh sách nối đơn, trướchết ta tạo ra một nút mới NewNode chứa giá trị v và cho nút này liên kết tới p Nếu p lànút đầu tiên của danh sách (head) thì cập nhật lại head bằng NewNode, còn nếu p không

Trang 3

phải nút đầu tiên của danh sách, ta tìm nút q là nút đứng liền trước nút p và chỉnh lại liênkết: q liên kết tới NewNode thay vì liên kết tới thẳng p.

procedure Insert(p: PNode;const v: TElement);

var NewNode, q: PNode;

begin

New(NewNode); NewNodệinfo := v; NewNodệlink := p;

if head = p then head :=NewNode

else begin

q := head;

while q^.link <> p do q := q^.link;

q^.link := NewNode; end;

end;

c Xóa phần tử khỏi danh sách nối đơn

Để xóa nút p khỏi danh sách nối đơn, gọi next là nút đứng liền sau p trong danh sách Xéthai trường hợp:

- Nếu p là nút đầu tiên trong danh sách head = p thì ta đặt lại head bằng next

- Nếu p không phải nút đầu tiên trong danh sách, tìm nút q là nút đứng liền trước nút p vàchỉnh lại liên kết: q liên kết tới next thay vì liên kết tới p

- Việc cuối cùng là huỷ nút p

procedure Delete(p: PNode);

var next, q: PNode;

while q^.link <> p do q := q.link;

q^.link := next; end;

Dispose(p);

end;

Trang 4

Ta có thể cài đặt danh sách nối đơn bảng mảng Mỗi nút chứa trong một phần tử của mảng

và trường Next là chỉ số nút kế tiếp

const MaxNode= 1000; // Kich thuoc toi da cua DS

Type TNode = record; //Kiểu biến động chứa thông tin trong một nút

info: TElement;

link: Integer;

end;

TList = array[1 max] of TNode ;

Var Nodes: TList

head: Integer;

d Một số loại danh sách khác

- Biểu diễn danh sách bằng danh sách nối vòng đơn

- Biểu diễn danh sách bằng danh sách nối kép

- Biểu diễn danh sách bằng danh sách nối vòng kép

3 Ngăn xếp

Ngăn xếp (Stack) là một kiểu danh sách mà việc bổ sung một phần tử và loại bỏ một phần

tử được thực hiện ở cuối danh sách Có thể hình dung ngăn xếp như một chồng đĩa, đĩanào được đặt vào chồng sau cùng sẽ nằm trên tất cả các đĩa khác và sẽ được lấy ra đầutiên Vì nguyên tắc “vào sau ra trước”, ngăn xếp còn có tên gọi là danh sách kiểu LIFO(Last In First Out) Vị trí cuối danh sách được gọi là đỉnh (top) của ngăn xếp

Tương tự như danh sách, ta gọi kiểu dữ liệu của các phần tử sẽ chứa trong ngăn xếp vàhàng đợi là TElement Khi cài đặt chương trình cụ thể, kiểu TElement có thể là kiểu sốnguyên, số thực, ký tự, hay bất kỳ kiểu dữ liệu nào được chương trình dịch chấp nhận.Đối với ngăn xếp có sáu thao tác cơ bản:

- Init: Khởi tạo một ngăn xếp rỗng

- Isempty: Cho biến ngăn xếp có rỗng không?

- IsFull: Cho biết ngăn xếp có đầy không?

- Get: Đọc giá trị phần tử ở đỉnh ngăn xếp

- Push: Đẩy một phần tử vào ngăn xếp

- Pop: Lấy ra một phần tử từ ngăn xếp

Trang 5

a Cài đặt bằng mảng

Cách biểu diễn ngăn xếp bằng mảng cần có một mảng items để lưu các phần tử trongngăn xếp và một biến nguyên top để lưu chỉ số của phần tử tại đỉnh ngăn xếp

Các khai báo dữ liệu:

const max = 1000 //Dung lượng cực đại của ngăn xếp

type TStack = record

items: array[1 max] of TElement;

top: Integer;

end;

var Stack: TStack;

Sáu thao tác cơ bản của ngăn xếp có thể viết như sau:

if IsEmpty then “Stack is Empty” //Báo lỗi ngăn xếp rỗng

else with Stack do Result := items[top];

end;

Trang 6

procedure Push(const x: TElement);

begin

if IsFull then "Stack is Full" //Báo lỗi ngăn xếp đầy

else with Stack do

begin

top := top + 1; //Tăng chỉ số đỉnh Stack

items [top] := x; //Đặt x vào vị trí đỉnh Stack

end;

end;

function Pop: TElement;

begin

if IsEmpty then "Stack is Empty" //Báo lỗi ngăn xếp rỗng

else with Stack do

begin

Result := items[top]; //Trả về phần tử ở đỉnh ngăn xếp

top := top - 1; //Giảm chỉ số đỉnh ngăn xếp

Type PNode = ^TNode ; //Kiểu con trỏ liên kết giữa các nút

TNode = record //Kiểu dữ liệu cho một nút

Trang 7

c Ứng dụng

Stack thường dùng để chuyển biểu thức trung tố thành hậu tố, tính biểu thức hậu tố vàdùng để khử đệ quy

Quy tắc chuyển trung tố thành hậu tố :

- Gán biểu thức hậu tố bằng xâu rỗng H = “”;

- Đọc biểu thức trung tố T từ trái qua phải

+ Nếu gặp dấu ngoặc mở “(“ thì nạp vào ngăn xếp

+ Nếu gặp dấu ngoặc đóng “)“ thì lấy các phần tử ở đỉnh ngăn xếp nối vào đuôibiểu thức hậu tố cho tới khi gặp dấu ngoặc mở thì bỏ dấu ngoặc này khỏi ngăn xếp

+ Nếu gặp dấu phép toán thì so phép toán này với phép toán ở đỉnh ngăn xếp (nếucó) Nếu phép tón này ưu tiên nhỏ hơn thì lấy phép toán ở đỉnh ngăn xếp cho vào đuôi H,đồng thời nạp phép toán này vào ngăn xếp Trong trường hợp ngược lại hoặc đỉnh ngănxếp không có phép toán thì nạp phép toán này vào đỉnh ngăn xếp

+ Nếu gặp toán hạng thì nối toán hạng vào đuôi H

- Sau khi đọc xong T, lần lượt lấy nốt các phần tử đỉnh ngăn xếp nối vào đuôi H cho tớikhi rỗng hoặc còn một dấu ngoặc “(“

Tính biểu thức hậu tố

- Đọc biểu thức hậu tố H từ trái qua phải

+ Nếu gặp toán hạng thì cho toán hạng vào ngăn xếp

+ Nếu gặp phép toán thì lấy khỏi ngăn xếp toán hạng thứ nhất và thứ hai sau đóđem toán hạng thứ hai thực hiện phpé toán với toán hạng thứ nhất, thu được kết quả chovào ngăn xếp

+ Số cuối cùng còn lại trong ngăn xếp là kết quả

4 Hàng đợi

Hàng đợi (Queue) là một kiểu danh sách mà việc bổ sung một phần tử được thực hiện ởcuối danh sách và việc loại bỏ một phần tử được thực hiện ở đầu danh sách

Khi cài đặt hàng đợi, có hai vị trí quan trọng là vị trí đầu danh sách (/ront), nơi các phần

tử được lấy ra, và vị trí cuối danh sách (rear), nơi phần tử cuối cùng được đưa vào

Có thể hình dung hàng đợi như một đoàn người xếp hàng mua vé: Người nào xếp hàngtrước sẽ được mua vé trước

Trang 8

Vì nguyên tắc “vào trước ra trước”, hàng đợi còn có tên gọi là danh sách kiểu FIFO (First

In First Out)

Tương tự như ngăn xếp, có sáu thao tác cơ bản trên hàng đợi:

- Init: Khởi tạo một hàng đợi rỗng

- IsEmpty: Cho biến hàng đợi có rỗng không?

- IsFull: Cho biết hàng đợi có đầy không?

- Get: Đọc giá trị phần tử ở đầu hàng đợi

- Pusp: Đẩy một phần tử vào hàng đợi

- Pop: Lấy ra một phần tử từ hàng đợi

a Biểu diễn hàng đợi bằng mảng

Ta có thể biểu diễn hàng đợi bằng một mảng items để lưu các phần tử trong hàng đợi, mộtbiến nguyên front để lưu chỉ số phần tử đầu hàng đợi và một biến nguyên rear để lưu chỉ

số phần tử cuối hàng đợi

Chỉ một phần của mảng items từ vị trí front tới rear được sử dụng lưu trữ các phần tửtrong hàng đợi

Các khai báo dữ liệu:

const max = 1000; //Dung lượng cực đại

type TQueue = record

items: array[1 max] of TElement;

front, rear: Integer;

end;

var Queue: TQueue;

Sáu thao tác cơ bản trên hàng đợi có thể viết như sau:

Trang 9

function IsEmpty: Boolean;

if IsFull then "Queue is Full" //Báo lỗi hàng đợi đầy

else with Queue do

Trang 10

b Biểu diễn hàng đợi bằng danh sách vòng

Bình thường mỗi lần đẩy phần tử vào ngăn xếp, chỉ số cuối hàng đợi rear luôn tăng lên vàkhông bao giờ bị giảm đi cả Đó chính là nhược điểm khi cài đặt: Chỉ có các phần tử từ vịtrí front tới rear là thuộc hàng đợi, các phần tử từ vị trí 1 tới front - 1 là vô nghĩa

Để khắc phục điều này, ta có thể biểu diễn hàng đợi bằng một danh sách vòng (dùngmảng hoặc danh sách nối vòng đơn): coi như các phần tử của hàng đợi được xếp quanhvòng tròn theo một chiều nào đó (chẳng hạn chiều kim đồng hồ) Các phần tử nằm trênphần cung tròn từ vị trí front tới vị trí rear là các phần tử của hàng đợi Có thêm một biến

n lưu số phần tử trong hàng đợi Việc đẩy thêm một phần tử vào hàng đợi tương đươngvới việc ta dịch chỉ số rear theo chiều vòng một vị trí rồi đặt giá trị mới vào đó Việc lấy

ra một phần tử trong hàng đợi tương đương với việc lấy ra phần tử tại vị trí front rồi dịchchỉ số front theo chiều vòng

Để tiện cho việc dịch chỉ số theo vòng, khi cài đặt danh sách vòng bằng mảng, người tathường dùng cách đánh chỉ số từ 0 để tiện sử dụng phép chia lấy dư (modulus - mod) Các khai báo dữ liệu:

const max = 1000; //Dung lượng cực đại

type TQueue = record

items: array[0 max - 1] of TElement;

n, front, rear:Integer;

end;

var Queue: TQueue;

Sáu thao tác cơ bản trên hàng đợi cài đặt trên danh sách vòng được viết dưới dạng giả mãnhư sau:

Trang 11

function IsEmpty: Boolean;

Trang 12

c Ứng dụng

Thuật toán loang : là thuật toán tổ chức tìm kiếm các trạng thái liên tiếp xảy ra từ trạngthái ban đầu P1 tới trạng thái kết thúc Pn sao cho qua ít trạng thái trung gian nhất Banđầu nạp P1 vào hàng đợi, sau đó tìm các trạng thái xảy ra tiếp theo của P1 là P11, P12,…nạp vào hàng đợi, tiếp tục tìm các trạng thái của P11 là P111, P112,… rồi tiếp tục tìm cáctrạng thái của P12 là P121, P122,… quá trình tìm dừng khi thấy trạng thái đích Pn

Mô tả thuật toán :

Làm rỗng Q, khời tạo first, last

Nạp P1 vào đầu hàng đợi

Trong khi ( first<last) và (chưa tới Pn) thì

Lấy x ở đầu Q : Lay(x);

Tìm y chưa xét kề với x thỏa điều kiện bài toán

Nạp y vào cuối Q : Nap(y);

Đánh dấu trước y là x : Truoc(y):=x;

Xác định lại trạng thái , điều kiện bài toán

Sau khi tìm được Pn dựa vào quá trình đánh dấu tìm ra được dãy từ Pn về ngược lại P1.Hiện dãy này theo thứ tự ngược lại ta được kết quả là dãy P1 tới Pn qua ít trạng thái trunggian nhất

5 Heap

a Đặc điểm của Heap

Heap là 1 cấu trúc khá quen thuộc, là 1 dạng Priority Queue (hàng đợi có độ ưu tiên), ứngdụng to lớn trong nhiều dạng toán khác nhau

Heap thực chất là 1 nhị phân gần hoàn chỉnh thoả mãn các điều kiện sau:

- 1 nút chỉ có không quá 2 nút con

- Nút cha là nút lớn nhất, mọi nút con luôn có giá trị nhỏ hơn nút cha

Điều kiện quan hệ nhỏ hơn của nút con so với nút cha có thể được quy định trước tuỳ theobài toán, không nhất thiết phải là nhỏ hơn theo nghĩa toán học

Mặc dù được mô tả như 1 cây nhưng Heap lại có thể lưu trữ trong mảng, nút gốc là nút 1,nút con của nút i là 2 nút 2*i và 2*i+1 Nút cha của nút j là nút [j/2]

Trang 13

- Nút gốc luôn là nút lớn nhất [theo định nghĩa có trước]

- Độ cao của 1 nút luôn nhỏ hơn hoặc bằng O(logN) vì cây heap cân bằng

Ứng dụng chủ yếu của heap là chỉ tìm min, max trong 1 tập hợp động, nghĩa là tập có thểthay đổi, thêm, bớt các phần tử (Nhưng như vậy đã là quá đủ)

b Các thao tác thường dùng trong Heap

Upheap: Khi có 1 nút có giá trị khóa tăng lên thì có thể cấu trúc heap không còn được

bảo toàn nữa, nó có thể phải ở1 ví trí khác (do nó lớn hơn nên có thểphải gần gốc hơn).Cần có 2 biến cha và con để thực hiện quá trình chỉnh sửa heap, ta tưởng tượng nút conlúc đầu là vị trí cần chỉnh sửa, nếu nút cha của nó có giá trị khóa < giá trị khóa của nó thìđổi chỗ nút con cho nút cha, nút con mới lúc này là nút cha cũ

Cứ tiếp tục như thế đến khi gặp 1 vị trí mà nút cha có giá trị khóa >= nó hoặc vị trí nútcon hiện tại là 1 (nút gốc)

Downheap: Ngược lại với upheap, có thể có giá trị khóa của phần tử nào đấy giảm đi thì

phải tiến hành điều chỉnh lại heap Đầu tiên nút cha ởvị trí cần thay đổi Nếu giá trị khóa ởnút cha < giá trị khóa lớn nhất trong các nút con thì tiến hành đổi chỗ nút cha và nút con

có giá trị khóa lớn nhất này Nút cha mới là nút con cũ

Cứ tiếp tục như thếcho đến khi gặp 1 vị trí mà giá trị khóa của nút cha >= giá trị khóa củanút con lớn nhất hoặc là nút đó không có nút con nữa

Trang 14

If (con<nheap) and (h[con].key < h[con+1].key) then inc(con);

If (h[con].key <= x) then break;

Trang 15

Nếu trong quá trình xử lí, các bạn cần biêt số hiệu các phần tử thì thêm vào 1 mảng shnữa, lưu ý là khi nào có sự thay đổi vị trí của 1 nút thì cũng thay đổi giá trị của mảng shtheo để đảm bảo sh[h[v]]:=v;

Khi các bạn đã có trong tay 2 thủ tục upheap và downheap thì các thao tác còn lại có thểquy về 2 thủ tục này:

- Thêm 1 phần tử vào heap: Cho phần tử này nằm ở vị trí nheap +1 rồi thực hiện thao tácupheap

- Lấy 1 phần tử ra khỏi heap( thường là ta cần lấy phần tử ở gốc vì đây là phần tử nhỏnhất hoặc lớn nhất trong 1 đoạn): Gán phần tử cuối vào phần tử cần loại bỏ, đồng thờidec(nheap), sau đó coi như phần tử này bị thay đổi giá trị, giá trị khóa mà tăng lên thì thựchiện upheap còn không thì thực hiện downheap

Sau khi ta đã cài đặt được các thủ tục cần có của 1 heap, ta sẽ tìm hiểu ứng dụng của nókhi giải các bài toán trong tin học Như ta thấy, heap max hay heap min có thể cho ta lấyphần tử lớn nhất hoặc phần tử nhỏ nhất trong 1 đoạn Vì vậy nó rất hữu dụng trong các bàitoán mà ta cần phải truy vấn các phần tử lớn nhất hoặc các phần tử nhỏ nhất trên 1đoạn( chẳng hạn như các bài toán QHĐ thì ta thường cần tìm những giá trị này)

c Ứng dụng của Heap

Bài toán 1 Đường đi ngắn nhất

Cho 1 đồ thị có n đỉnh, m cạnh (1<=n,m<=10^5), tìm độ dài đường đi ngắn nhất giữa 2đỉnh s và t của đồ thị

Thuật toán:

Bài này với dữ liệu nhỏ(n<=5000) thì các bạn dùng thuật toán Dijkstra để tìm đường đingắn nhất từ đỉnh s Nhưng với n<=10^5 mà thuật toán Dijkstra cổ điển có độ phức tạpn^2 thì không khả thi, vậy ta phải tìm cách cải tiến

Trong thuật toán Dijkstra, mỗi lần ta cần tìm 1 đỉnh trong số các đỉnh chưa được đánh dấusao cho đỉnh này có giá trị đường đi hiện tại là nhỏ nhất Điều này làm ta nghĩ đến dùngheap Dùng 1 heap min lưu chỉ số của các đỉnh (giá trị đường đi ngắn nhất hiện tại lưutrong mảng d) Mỗi lần tìm đỉnh min, ta lại lấy đỉnh ở gốc ra, khi cập nhật nếu có đỉnhnào được thay đổi giá trị thì ta thực hiện upheap (hoặc thêm vào nếu phần tử này chưa có

Trang 16

trong heap) Để thực hiện cập nhật mảng d thì nên lưu đồ thị bằng danh sách liên kết hoặclưu kiểu Forward-star Độ phức tạp thuật toán là mlog(n)

Ví dụ 2 KMIN – KMIN – SPOJ

Cho 2 dãy số nguyên A và B Với mọi số A[i] thuộc A và B[j] thuộc B người ta tính tổng

nó Tất cả các tổng này sau khi được sắp xếp không giảm sẽ tạo thành dãy C

Nhiệm vụ của bạn là: Cho 2 dãy A, B Tìm K số đầu tiên trong dãy C

Input

Dòng đầu tiên gồm 3 số: M, N, K (1 ≤ M, N, K ≤ 50000)

M dòng tiếp theo gồm M số mô tả dãy A (1 ≤ Ai, Bi ≤ 109)

N dòng tiếp theo gồm N số mô tả dãy B

Trang 17

Thuật toán:Với m,n<=50000 thì ta không thể tạo 1 mảng 50000^2 rồi sắp xếp lại được,

vì vậy chúng ta cần những thuật toán tinh tế hơn, 1 trong những thuật toán đơn giản màhiểu quả nhất là dùng heap

Đầu tiên ra sắp xếp 2 mảng A và B không giảm, dĩ nhiên phần tử đầu tiên chính làA[1]+B[1], vấn đề là phần tử thứ 2 là A[2]+B[1] hay A[1]+B[2], ta xử lí như sau:

Trước hết ta tạo 1 heap min gồm các phần tử A[1]+B[1], A[1]+B[2],…A[1]+B[n], mỗikhi lấy ra phần tử ở gốc( tức là phần tử có giá trị nhỏ nhất trong heap hiện tại), giả sửphần tử đó là A[i]+B[j], ta lại đẩy vào heap phần tử mới là A[i+1]+B[j]( nếu i=m thìkhông đẩy thêm phần tử nào vào heap) Cứ thế cho đến khi ta lấy ra đủ k phần tử cần tìm

Độ phức tạp thuật toán trong trường hợp lớn nhất là O(50000*log(50000)), có thể chạytrong thời gian 1s, bộ nhớ lưu trữ tỷ lệ với n

Bài toán 3 Một chút về Huffman Tree – HEAP1 – SPOJ

Một người nông dân muốn cắt 1 thanh gỗ có độ dài L của mình thành N miếng , mỗimiếng có độ dài là 1 số nguyên dương A[i] ( A[1] + A[2] + … A[N] = L ) Tuy nhiên đểcắt một miếng gỗ có độ dài là X thành 2 phần thì ông ta sẽ mất X tiền Ông nông dân nàykhông giỏi tính toán lắm, vì vậy bạn được yêu cầu lập trình giúp ông ta cho biết cần đểdành ít nhất bao nhiêu tiền thì mới có thể cắt được tấm gỗ như mong muốn

Input

Dòng 1 : 1 số nguyên dương T là số bộ test

T nhóm dòng tiếp theo mô tả các bộ test , mỗi nhóm dòng gồm 2 dòng :

Trang 18

Đầu tiên cắt miếng gỗ thành 2 phần có độ dài 6 và 4 Sau đó cắt tiếp miếng có độ dài

6 -> 3 và 3 Cắt 1 miếng 3 thành 2 phần độ dài 1 , 2 Như vậy chi phí là 10 + 6 + 3 = 19

Bài toán 4: MEDIAN (phần tử trung vị).

Đề bài: Phần tử trung vị của 1 tập N phần tử là phần tử có giá trị đứng thứ N div 2 + 1 với

N lẻ và N div 2 hoặc N div 2+1 với N chẵn

Cho 1 tập hợp ban đầu rỗng Trong file Input có M<=10000 thao tác thuộc 2 loại:

1 PUSH x đưa 1 phần tử giá trị x vào trong HEAP (x<=10^9)

2 MEDIAN trả về giá trị của phần tử trung vị của tập hợp đó (nếu N chẵn trả về cả 2 giátrị)

Yêu cầu: viết chương trình đưa ra file OUTPUT tương ứng.

Input: dòng đầu tiên ghi số M, M dòng tiếp theo ghi 1 trong 2 thao tác theo định dạng

trên

Output: tương ứng với mỗi thao tác MEDIAN trả về 1 (hoặc 2) giá trị tương ứng.

Thuật giải: Dùng 2 heap, 1 heap (HA) lưu các phần tử từ thứ 1 tới N div 2 và heap còn

lại (HB) lưu các phần tử từ N div 2 +1 tới N sau khi đã sort lại tập thành tăng dần HA làHeap-max còn HB là Heap-min Như vậy phần tử trung vị luôn là gốc HB (N lẻ) hoặc gốccủa cả HA và HB (n chẵn) Thao tác MEDIAN do đó chỉ có độ phức tạp O(1) Còn thaotác PUSH sẽ được làm trong O(logN) như sau:

- Nếu x đưa vào nhỏ hơn hoặc bằng HA[1] đưa vào HA ngược lại đưa vào HB Số phần tử

N của tập tăng lên 1

- Nếu HA có lớn hơn N div 2 phần tử thì POP 1 phần tử từ HA đưa vào heap còn lại

- Nếu HA có nhỏ hơn N div 2 phần tử thì POP 1 phần tử từ HB đưa vào heap còn lại.Sau quá trình trên thì HA và HB vẫn đảm bảo đúng theo định nghĩa ban đầu

Trang 19

6 Interval tree

a Giới thiệu

Interval Tree là 1 cấu trúc vô cùng hữu dụng, được sử dụng rất nhiều trong các bài toán vềdãy số Ngoài ra Interval Tree còn được sử dụng trong 1 số bài toán hình học Có thể nóinếu nắm rõ Interval Tree bạn đã làm được 1 nửa số bài toán về dãy số rồi đấy

Interval tree là cây đoạn, vậy thì nó là 1 cây, và các nút của nó thì lưu thông tin của 1đoạn xác định Interval tree là 1 cây nhị phân mà mỗi nút không phải là lá đều có đúng 2nút con Nếu nút A lưu thông tin của đoạn từ i j thì 2 nút con của nó, A1 và A2, lần lượtlưu thông tin của các đoạn i m và m+1 j, với m=(i+j) div 2 là phần tử giữa của đoạn

Vì đây là cây nhị phân, lại có đầy đủ 2 nút con, để đơn giản thì ta có thể biểu diễn cây chỉbằng 1 mảng 1 chiều, với ý nghĩa như sau:

- Nút 1 là nút gốc, nút k ( nếu không phải là nút lá) thì có 2 con là các nút k*2 ( nút contrái) và k*2+1(nút con phải)

- Nút 1 sẽ lưu thông tin của đoạn 1 n ( với n là độ dài dãy số)

- Vậy nút 2 sẽ lưu thông tin đoạn 1 (n+1) div 2 và nút 3 sẽ lưu thông tin đoạn

Trang 20

Trên interval tree có 2 thao tác cần xử lí: 1 là cập nhật thay đổi cho đoạn i j và 2 là lấythông tin cần có của đoạn i j, nhưng mỗi nút của inteval tree chỉ lưu những đoạn xácđịnh, vì vậy 2 thao tác này có thể cần tác động đến nhiều nút.

b Các thao tác

Ví dụ : Cho 1 dãy n phần tử (n<=10^5), ban đầu mỗi phần tử có giá trị 0

Có q<=10^5 truy vấn, mỗi truy vấn là 1 trong 2 thao tác sau:

- 1 là gán giá trị v (v>0) cho phần tử ở vị trí i

- 2 là tìm giá trị lớn nhất trong đoạn i j bất kì

Cách dùng interval tree như sau:

Với truy vấn 1, ta sẽ cập nhật là kết quả max cho tất cả các đoạn mà chứa phần tử thứ i,những đoạn còn lại thì không làm gì cảvì không ảnh hưởng đến

Với truy vấn 2, ta cần tìm kết quả max trong số tất cả những đoạn nằm gọn trong i j, tức

là những đoạn có điểm đầu >=i và điểm cuối <=j

Gọi T[1 400000] of longint là mảng để lưu trữ cây interval tree T[i] là giá trị lớn nhấttrong đoạn mà nút k lưu giữ thông tin

Khi gặp truy vấn 1, ta làm như trong thủ tục sau:

Trang 21

Ta sẽ phân tích thủ tục truyvan1:

- Thủ tục này có các tham số k,l,r,i với ý nghĩa: l và r là điểm đầu và điểm cuối của đoạn

mà nút k lưu trữ thông tin, i chính là phần tử cần gán giá trị v

- Trong thủ tục có 1 biến m để xác định phần tử nằm giữa đoạn l r

- Câu lệnh 1 có ý nghĩa là ta sẽ bỏ qua không làm gì với các đoạn không chứa phần tử thứi( l>i hoặc r<i)

- Câu lệnh 2 tức là, khi đã đến nút thứ k biểu diễn đoạn i i thì ta chỉ cần gán T[k]=v, vì tấtnhiên sau khi gán, v sẽ là giá trị lớn nhất của đoạn i i

- Câu lệnh 3 xác định phần tử nằm giữa l r

- Câu lệnh 4 là 1 lời gọi đệ quy, để ý các tham số thì dễ dàng nhận ra, câu lệnh này gọiđến nút con trái của nút k, tức nút k*2 để ta cập nhật cho nút đó

- Câu lệnh 5 tương tự câu lệnh 4 nhưng là gọi đểcập nhật cho nút con phải

- Câu lệnh 6: Sau khi đã cập nhật cho 2 nút con trái và phải thì đã xác định được giá trịlớnnhất từ l m và m+1 r, vậy thì để xác định giá trịlớn nhất của đoạn l r ta chỉ cần lấy giá trịlớn nhất của 2 đoạn kia=> T[k]:=max(T[k*2],T[k*2+1])

Khi gọi thủ tục truy vấn 1, ta sẽ gọi từ nút gốc, tức là gọi truyvan1(1,1,n,i);

để xem chương trình có hoạt động tốt hay không lại là kết quả của yêu cầu thứ 2, vì vậythủ tục truyvan1 là để giúp thủ tục truyvan2 sau đây tìm được kết quả với đoạn i j bất kì.Procedure truyvan2(k,l,r,i,j:longint);

var m:longint;

begin

1.if (l>j)or(r<i) then exit;

2.if (i<=l)and(j>=r) then

Trang 22

Phân tích: Thủ tục truy vấn 2 này có các tham số với ý nghĩa như thủ tục 1, có thêm tham

số j vì cần lấy kết quả trên đoạn i j

Mỗi đoạn l r sẽ có 3 khả năng với đoạn i j

- a: l r không giao i j, trường hợp này bỏ qua không làm gì cả( câu lệnh 1)

- b: l r nằm gọn trong i j, trường hợp này thì ta chỉ cần tối ưu kết quả khi so sánh vớiT[k] vì: Ta đã xác định được phần tử lớn nhất trong đoạn l r nhờ thủ tục 1, và do l r nằmgọn trong i j nên không cần đi vào 2 nút con của nó.(câu lệnh 2)

- c: l r không nằm gọn trong i j nhưng có 1 phần giao với i j, khi này thì ta sẽ đi vào 2nút con của nó đểlấy kết quả, đến khi nào gặp phải 2 trường hợp đầu.(câu lệnh 4 và 5) Suy nghĩ kĩ sẽ thấy thủ tục truy vấn 2 không bỏ sót cũng như tính thừa phần tử nào ngoàiđoạn i j

Khi gọi thủ tục truyvan2 ta cũng gọi từ nút gốc truyvan2(1,1,n,i,j) Sau thủ tục thi in ra res

là kết quả cần tìm

Người ta cũng chứng minh được rằng, độ phức tạp của mỗi thủ tục truyvan1 và truyvan2không quá log(n) Vì vậy thuật toán dùng interval tree cho bài này có độ phức tạp khôngquá q*log(n)=10^5*log(10^5), có thể chạy trong thời gian cho phép

Đây là 1 trong những ví dụ cơ bản nhất về interval tree, hi vọng các bạn đã có thể hiểu vàcài đặt nó.Trong khi nghiên cứu về các bài tập sau đây, ta sẽ tìm hiểu 1 vài kiểu sử dụnginterval tree khác Đây cũng là 1 trong những cấu trúc dữ liệu thường được sử dụng trongcác kì thi Olympic Tin học trên thế giới

c Các ví dụ

Bài toán 1 : Xếp hàng(NKLINEUP)

Hàng ngày khi lấy sữa, N con bò của bác John (1 ≤N ≤50000) luôn xếp hàng theo thứ tựkhông đổi Một hôm bác John quyết định tổ chức một trò chơi cho một số con bò Đểđơn giản, bác John sẽ chọn ra một đoạn liên tiếp các con bò để tham dự trò chơi Tuynhiên để trò chơi diễn ra vui vẻ, các con bò phải không quá chênh lệch về chiều cao Bác John đã chuẩn bị một danh sách gồm Q (1 ≤Q ≤200000) đoạn các con bò và chiềucao của chúng (trong phạm vi [1, 1000000]) Với mỗi đoạn, bác John muốn xác địnhchênh lệch chiều cao giữa con bò thấp nhất và cao nhất Bạn hãy giúp bác John thực hiệncông việc này!

Trang 23

Dữ liệu

Dòng đầu tiên chứa 2 sốnguyên N và Q

Dòng thứ i trong số N dòng sau chứa 1 số nguyên duy nhất, là độ cao của con bò thứ i.Dòng thứ i trong số Q trong tiếp theo chứa 2 số nguyên A, B (1 ≤A ≤B ≤N), cho biết đoạncác Con bò từ A đến B

Kết qủa

Gồm Q dòng, mỗi dòng chứa 1 số nguyên, là chênh lệch chiều cao giữa con bò thấp nhất

và cao nhất thuộc đoạn tương ứng

Đây là 1 bài toán xử lí trên dãy số và cần truy cập đến những đoạn A B bất kì trong dãy,

vì vậy interval tree là 1 trong những lựa chọn không tồi Bài này chúng ta có 1 điều maymắn là không cần phải cập nhật lại chiều cao của các con bò, vì vậy thông tin trong câyinterval tree là cố định và ta sẽ tạo cây interval tree dựa trên mảng chiều cao của các con

bò Mỗi đoạn thì ta cần in ra chênh lệch độ cao con bò cao nhất và con bò thấp nhất, vì

Trang 24

vậy chúng ta cần tìm được giá trị lớn nhất và giá trị nhỏ nhất trong các phần tử từ A đến

B Ta có thể dùng 1 cây interval tree với mỗi nút lưu 2 thông tin, giá trị lớn nhất và giá trịnhỏ nhất trong đoạn mà nó biểu diễn, cũng có thể dùng 2 cây interval tree, 1 cây dùng đểlưu giá trị lớn nhất, cây còn lại là giá trị nhỏ nhất Ở đây ta gọi 2 cây này là maxd vàmind Phần tạo ta có thể làm rất đơn giản dựa trên ý tưởng sau:

chỉ cần gọi chung 1 thủ tục

Bài toán 2 : Giá trị lớn nhất(QMAX)

Cho một dãy gồm n phần tử có giá trị ban đầu bằng 0

Cho m phép biến đổi, mỗi phép có dạng (u, v, k): tăng mỗi phần tử từ vị trí u đến vị trí vlên k đơn vị

Cho q câu hỏi, mỗi câu có dạng (u, v): cho biết phần tử giá trị lớn nhất thuộc đoạn [u, v]

Ngày đăng: 25/09/2019, 05:38

TỪ KHÓA LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w