1. Trang chủ
  2. » Luận Văn - Báo Cáo

Chuyên đề HIỆU QUẢ sử DỤNG cấu TRÚC INTERVAL TREE

25 467 3

Đ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 25
Dung lượng 3,26 MB

Nội dung

HIỆU QUẢ SỬ DỤNG CẤU TRÚC INTERVAL TREEI.Đặt vấn đề Các bài toán trên dãy số đang xuất hiện nhiều trong các đề thi olympic tin học quốc gia và quốc tế, Có rất nhiều cấu trúc lưu trữ được

Trang 1

HIỆU QUẢ SỬ DỤNG CẤU TRÚC INTERVAL TREE

I.Đặt vấn đề

Các bài toán trên dãy số đang xuất hiện nhiều trong các đề thi olympic tin học quốc gia và quốc

tế, Có rất nhiều cấu trúc lưu trữ được sử dụng để xử lý đối với các bài toán dạng này, Theo tìm hiểu cá nhân tôi thì một trong các cấu trúc lưu trữ hiệu quả với các thao tác xử lý ít tốn thời gian hơn các cấu trúc khác đó là INTERVAL TREE (Cây đoạn)

Cây đoạn là một công cụ rất hữu dụng được sử dụng nhiều trong các bài toán trên dãy số, hoặc được quy về các bài toán xử lý trên dãy số, đặc biệt là các bài toán có nhiều công việc cần xử lí và nhiều truy vấn xen kẻ nhau

Chính vì vậy, Tôi viết chuyên đề này nhằm làm rõ mức độ hiệu quả khi sử dụng cấu trúc interval tree thông qua một số bài tập có thể áp dụng cấu trúc này để giải quyết

II.Nội dung

1.Lý thuyết nền tảng

Cây phân đoạn là một cây, các nút của nó lưu thông tin của một đoạn xác định Interval tree là một cây nhị phân mà mỗi nút không phải là lá đều có đúng 2 nú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ượt lư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 con trá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 của 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 (n+1)div 2 +1 n.Tương tự theo định nghĩa thì ta có thể biết được nút thứ k sẽ lưu thông tin của một đoạn xác định nào đó Sau đây là hình ảnh 1 cây INTERVAL TREE với n =7:

Trang 2

Có một vấn đề là với 1 đoạn n phần tử thì mảng biễu diễn cây sẽ có bao nhiêu phẩn tử là đủ, người ta đã chứng minh được cây interval tree chỉ cần tối đa là 4*n-5 phần tử, vì vậy khi khai báo

1 cây interval tree ta thường khai báo 1 mảng 1 4*n

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ấy thông tin cần có của đoạn i j, những mỗi nút của interval trê 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

Để dễ hình dung, ta lấy 1 ví dụ cụ thể:

Cho 1 dãy n phần tử (n<= 10^5), ban đầu mỗi phần tử có giá 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 đơn giản nhất là ta có 1 mảng A[1 100000], với thao tác 1 thì gán A[i]=v, thao tác 2 thì cho

1 vòng for từ i đến j để tìm max, nhưng cách này trong trường hợp có nhiều truy vấn thứ thì sẽ chạy rất lâu, trừng hợp xấu nhất là n*q Dễ thấy cách này không thể chạy trong thời gian cho phép

Cách dùng interval 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 đoạn 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 tron 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ất trong đ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 3

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 khi đã đến nút thứ k biểu diễn đoạn i i thì ta cần gán T[k]= v, vì tất nhiê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ớn nhấ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(l,l,n,i);

Vậy là đã xong yêu cầu thứ nhất, nhưng cái ta cần để 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ậy thủ 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ì,

Trang 4

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

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ới T[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ằm gọ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 trong đoạn i j nhưng có 1 phần giao với i j, trường hợp này thì ta sẽ đi vào 2

nú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(l,l,n,i,j) Sau thủ tục thì 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à truyvan2 không quá log(n) Vì vậy thuật toán dùng interval tree cho bài toán này có độ phức tạp không quá 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ụng interval tree khác

Đây cũng là 1 trong những cấu trúc dữ liệu thường găp được sử dụng trong các kì thi Olympic Tin học trên thế giới Các bài tập này sẽ được trình bày trong phần bài tập ứng dụng

Trang 5

2.Bài tập ứng dụng

Bài toán 1: Dãy con tăng dài nhất

Cho dãy số a a1, , ,2 a Hãy tìm dãy con (không nhất thiết gồm các phần tử liên tiếp) tăng dài n

nhất

Đây là bài toán qui hoạch động quen thuộc: Đặt f[i] là độ dài dãy con tăng dài nhất kết thúc tại ai

Ta có công thức qui hoạch động sau:

[ ] max [ ]:{ , k i} 1

Có nhiều cách để tính toán (1) trong thời gian O(log n) Một trong những cách như vậy là sử dụng

tìm kiếm nhị phân Ở đây, chúng ta tiếp cận theo một cách khác:

Trước tiên giả thiết rằng a i∈1, 2, ,n với i=1,2, ,n Bất đẳng thức a k <a icó thể viết dưới dạng [1 1]

aa Do đó việc tính (1) có thể qui về việc tính lần lượt f[1], f[2], và với mỗi i=1,2, ,n thì f[i] được tính bằng cách lấy giá trị lớn nhất của các giá trị f đã được tính có điểm cuối thuộc [1 ai-1] (mỗi lần có được giá trị f[i] ta ghi nhận nó vào vị trí ai[1 n]) và ta có thể sử

dụng IT để thực hiện các truy vấn tìm max này) Dưới đây là mã chương trình viết bằng IT:

void update(int r,int k,int l,int u,int v,int val) {

Trang 6

int get(int r,int k,int u,int v) {

if (v<k || u>l) return -INF; // INF là giá trị đủ lớn

if (u<=k && l<=v) return it[r]+dt[r];

Trang 7

Dễ thấy việc tìm dãy con tăng dài nhất trên dãy a a1, , ,2 a hoàn toàn giống như việc tìm dãy con n

tăng dài nhất trên dãy b b1, , ,2 b Đoạn mã dưới đây làm công việc này: n

for(int i=1;i<=n;i++) x[i]=a[i];

sort(x+1,x+n+1);

for(int i=1;i<=n;i++) b[i]=lower_bound(x+1,x+n+1,a[i])-x;

Nếu code bằng Pascal, trước tiên ta sắp xếp lại mảng A theo chỉ số:a[id[1]] ≤a[id[2]]≤ a[id[n]] Sau đó thực hiện đoạn mã sau:

Trang 8

Output: COVER.OUT

• Gồm một dòng duy nhất là đáp số của bài toán

Thời gian chạy: 0.5s

Trang 9

Hướng dẫn:

Để tiện cho việc phân tích bài toán, chúng ta sẽ gọi hoành độ của các đỉnh hình chữ nhật là H1, H2,

…, H2*N được sắp xếp theo chiều tăng; tung độ của các đỉnh hình chữ nhật là T1, T2, …, T2*N (giả sử các tung độ được liệt kê theo chiều tăng - ở đây chú ý rằng: chỉ có hoành độ được sắp xếp tăng, còn tung độ là do chúng ta quy ước) Cách làm thông thường là sắp xếp H theo chiều tăng như đã giả sử ở trên, sau đó tính diện tích ở từng khe H1H2, H2H3, …, H2*N-1H2*N Như vậy, độ phức tạp của thuật toán của chúng ta là O(N2), thời gian là T(N(log2N + N)), khó có thể đáp ứng thời gian chạy

là 1s Để cho tiện, tất cả dữ liệu của chúng ta đều coi như sắp xếp bằng Quick Sort

Trong hoàn cảnh đó, rất may mắn, chúng có một cấu trúc dữ liệu đặc biệt khác có thể thực hiện được điều này với độ phức tạp O(Nlog2N) và thời gian chạy là T(2Nlog2N) Đó là dùng Interval Tree, bản chất của Interval hết sức đơn giản, chúng ta sẽ xét rõ hơn qua phân tích chi tiết bài toán này

Thuật toán của chúng ta về bản chất cũng giống với thuật toán vừa đề cập:

• Sắp xếp các hoành độ tăng dần

• Tính diện tích che phủ từng khe H1H2, H2H3, …, H2*N-1H2*N của hoành độ, lấy tổng, ta được đáp số của bài toán

Trước hết, ta gán cho mỗi hoành độ đã được sắp xếp một nhãn, nhãn đó sẽ gồm Low, High lưu lại tung

độ của hình chữ nhật chứa hoành độ đó và nhãn Open Open sẽ chứa giá trị true nếu đó là đỉnh trên trái của hình chữ nhật, false nếu đó là đỉnh dưới phải của hình chữ nhật

Với đề bài, ta sẽ có các hoành độ được gán nhãn (sau khi đã sắp xếp) như sau:

H[1] = -2; Label[1].Low = 0; Label[1].High = 2;

Label[1].Open = true; H[2] = -1; Label[2].Low = 2;

Label[2].High = 5; Label[2].Open = true;

Tiếp theo ta sẽ xây dựng một cây nhị cây nhị phân đầy đủ, mỗi đỉnh sẽ tượng trung cho số hình chữ nhật che phủ trên đoạn [A, B] thuộc tung độ, mỗi đỉnh gồm có 2 phần Number và Cover với ý nghĩa:1) Number: số lượng hình chữ nhật che phủ toàn bộ [A, B]

2) Cover: tổng số đoạn tung độ bị che phủ trên đoạn [A, B]

9

Trang 10

Đỉnh 1 của cây có đoạn [A, B] tương ứng là [Min, Max] (với Min, Max tương ứng là tung độ nhỏ nhất và lớn nhất trong số các hình chữ nhật); đỉnh 2 có đoạn tương ứng [A, B] là [1, Max div 2];

đỉnh 3 là [Max div 2, Max],… tạo thành một cây nhị phân đầy đủ Đơn giản hơn, với mỗi đỉnh chứa

thông tin về đoạn [A, B], hai nút con tương ứng của nó sẽ lưu trữ thông tin về đoạn [A, (A+B) div 2] và

[(A+B) div 2, B] Sở dĩ đỉnh con thứ hai không xét từ [(A+B) div 2 + 1, B] bởi lẽ chúng ta đang xét theo

tung độ chứ không phải là xét theo độ dài đoạn thẳng, nếu xét như vậy ta sẽ không bị sót đoạn [(A+B)

div 2, (A+B) div 2 + 1]

Quá trình xây dựng cây nhị phân sẽ được đồng nhất với quá trình tính diện tích, có nghĩa là với mỗi khe H1H2, H2H3, …, H2*N-1H2*N chúng ta sẽ xây dựng cây nhị phân tương ứng với hình chữ nhật đang xét Lưu ý rằng “hình chữ nhật đang xét” ở đây nói về hình chữ nhật liên quan đến các hoành độ

H sau khi đã sắp xếp, bởi như đã nói ở trên, H được gán nhãn để đại diện cho một đỉnh của hình chữ nhật

Giả sử rằng chúng ta đang xét đến khe thứ i và i-1, đỉnh k của cây mang đoạn [A, B], có thể xảy ra 2 trường hợp sau:

1 Đỉnh H[i] đang xét là đỉnh trái trên của hình chữ nhật: Có 3 khả năng xảy ra:

Nếu hình chữ nhật che phủ đoạn [A, B], tức là (Low ≤ A) and (B ≤ High):

Trang 11

Khi đó số hình chữ nhật bị che phủ phải tăng lên 1 và tổng số che phủ chính bằng High – Low

Ta không cần phải xét đến các nút con của nó

Nếu hình chữ nhật nằm hoàn toàn ngoài đoạn [A, B], tức là (High ≤ A) or (B ≤ Low):

(vì chúng ta đang xét đến tung độ nên phải thêm “=” bởi nếu có “=” thì số phần bị che phủ cũng không tăng lên):

Khi đó, chắc chắn hình chữ nhật cũng không che phủ lên các nút con của nó, ta không phải làm gì cả

• Nếu hình chữ nhật giao với đoạn [A, B], tức là trường hợp còn lại:

Ta xét đến các nút con của nút k Nếu như số hình chữ nhật bao phủ lên k là 0 chứng

tỏ tổng số che phủ của k bằng tổng số che phủ của các nút con của nó

Procedure Open_True(A, B, k, Low, High);

{Nằm ngoài} {Giao nhau}

if (High <= A) or (B <= Low) then exit;

{Xét nút con trái}

11

Trang 12

Open_True(A, (A+B) div 2, 2*k, Low, High);

2 Đỉnh H[i] đang xét là đỉnh phải dưới của hình chữ nhật: Có 3 khả năng xảy ra:

Nếu hình chữ nhật che phủ đoạn [A, B], tức là (Low ≤ A) and (B ≤ High):

Khi đó số hình chữ nhật bị che phủ phải giảm lên 1 Nếu như không còn hình chữ nhật nào che phủ

nó thì tổng số che phủ bằng tổng số che phủ của các nút con của nó Ta không cần phải xét đến các nút con

Nếu hình chữ nhật nằm hoàn toàn ngoài đoạn [A, B], tức là (High ≤ A) or (B ≤ Low): (vì

chúng ta đang xét đến tung độ nên phải thêm “=” bởi nếu có “=” thì số phần bị che phủ cũng không tăng lên): Khi đó, chắc chắn hình chữ nhật cũng không che phủ lên các nút con của k, ta không phải làm gì cả

• Nếu hình chữ nhật giao với đoạn [A, B], tức là trường hợp còn lại:

Ta xét đến các nút con của nút k Nếu như số hình chữ nhật bao phủ lên k là 0 chứng tỏ tổng số che phủ của k bằng tổng số che phủ của các nút con của nó

Procedure Open_False(A, B, k, Low, High);

Trang 13

if Number[k] = 0 then Cover[k] := Cover[2*k] + Cover[2*k+1];

Trang 14

• Một số bạn sẽ cho rằng nếu như tại một hoành độ có nhiều đỉnh đóng hoặc nhiều đỉnh

mở thì sao? Câu trả lời là không sao cả, bởi lẽ lúc đó H[i] – H[i-1] sẽ bằng 0 và diện tích

sẽ không được tính vào Area Thêm nữa, các đỉnh có chung hoành độ sẽ được mở hoặc đóng cho đến hết rồi mới được thực sự tính vào diện tích

Cần phân biệt Number là số hình chữ nhật phủ lên đoạn [A, B] trong một đỉnh của cây chứ không phải số hình chữ nhật được đoạn [A, B] phủ Sở dĩ chúng ta cần đến

Number bởi nếu có một hình chữ nhật phủ kín đoạn [A, B]

• Nhiều bạn mới đọc sẽ thắc mắc: Chúng ta đang xét đến các khe, làm sao có thể xét được hết các hình chữ nhật có chung hoành độ được? Câu trả lời nằm ở chú ý thứ nhất: tất cả

Trang 15

các hình chữ nhật có chung hoành độ, bất kể là đỉnh trên trái hay dưới phải đều được xét hết trước khi diện tích được cộng chính thức vào tổng

Loại cây mà chúng ta vừa sử dụng được gọi là Interval Tree Vậy, Interval Tree là gì? Có thể nói vắn tắt lại rằng Interval Tree thực chất là một loại cây nhị phân đầy đủ với mỗi đỉnh chứa các thông tin về một đoạn [A, B] xác định 2 con của nó chứa thông tin về đoạn [A, (A+B) div 2] và [(A+B) div 2 + 1, B] Tùy theo mục đích sử dụng Interval Tree mà chúng ta có các loại Interval Tree khác nhau

Mấu chốt của bài toán là chúng ta nhận ra được tầm quan trọng của việc xác định đoạn mở và đoạn đóng trên một trục nhất định (ở bài toán trên chúng ta chọn trục tung) Theo cách giải thông thường, chúng ta phải lặp để xác định sự chồng chéo nhau của các đoạn nguyên, độ phức tạp của thuật giải thông thường là rất cao Nhưng giờ đây với Interval Tree, độ phức tạp chỉ là O(log2N), với trường hợp xấu nhất

Bài tập 3: 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 Tuy nhiê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 số danh sách gồm Q (1 Q 200000) đoạn các con bò và chiều cao của chúng (trong phạm vi [1,1000000]), Với mỗi đoạn, bác John muốn xác định chênh lệch chiều cao giữ con bò thấp nhất và cao nhất Bạn hãy giúp bác John thực hiện công việc này!

Dữ liệu

• Dòng đầu tiên chứ 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 dòng tiếp theo chứa 2 số nguyên A,B (1 A B N), cho biết đoạn các con bò từ A đến B

Ngày đăng: 19/01/2016, 19:05

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w