1. Trang chủ
  2. » Trung học cơ sở - phổ thông

CHUYÊN đề cấu TRÚC dữ LIỆU NÂNG CAO

28 32 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 28
Dung lượng 53,13 KB

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

Nội dung

Interval Tree là 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. Phần lí thuyết về Interval Tree đã được trình bày rất rõ ràng ở nhiều tài liệu do các chuyên gia, đồng nghiệp dạy bồi dưỡng học sinh giỏi chia sẻ, nên tôi mạn phép không đề cập tại đây. Do năng lực có hạn nên tôi không viết hoặc nghĩ ra những bài tập mới mà có sử dụng Interval Tree để giải được. Vì thế, trong chuyên đề này thực chất là các bài tập tôi sưu tầm, biên tập thành tập tài liệu để phục vụ trong công tác giảng dạy bồi dưỡng HSG môn Tin học. Ở đây, tôi trích dẫn các bài tập nguồn từ SPOJ, Codeforce và nhiều nguồn khác. Với mỗi bài tập tôi đề cập đến ba vấn đề: • Tóm tắt đề bài rõ ràng • Thuật toán tốt • Code demo (nếu có). Khi áp dụng tài liệu này vào giảng dạy, tôi thường bỏ phần “code demo” để không “làm hỏng học sinh”, chỉ phát đề cho học sinh. Với mỗi bài tập, sau khi học sinh nghiên cứu và đề xuất ý tưởng (hoặc code nộp mà chưa AC), tôi dẫn dắt, đưa ra giải thuật của bài toán đó để học sinh “ngấm” bài toán hơn. Dần dần học sinh nắm được tư tưởng Interval Tree và ứng dụng linh động vào các bài toán khác.

Trang 1

CHUYÊN ĐỀ: CẤU TRÚC DỮ LIỆU NÂNG CAO

Interval Tree là 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ềucông việc cần xử lí và nhiều truy vấn xen kẽ nhau

Phần lí thuyết về Interval Tree đã được trình bày rất rõ ràng ở nhiều tài liệu do cácchuyên gia, đồng nghiệp dạy bồi dưỡng học sinh giỏi chia sẻ, nên tôi mạn phépkhông đề cập tại đây

Do năng lực có hạn nên tôi không viết hoặc nghĩ ra những bài tập mới mà có sử dụngInterval Tree để giải được Vì thế, trong chuyên đề này thực chất là các bài tập tôi sưutầm, biên tập thành tập tài liệu để phục vụ trong công tác giảng dạy bồi dưỡng HSGmôn Tin học Ở đây, tôi trích dẫn các bài tập nguồn từ SPOJ, Codeforce và nhiềunguồn khác Với mỗi bài tập tôi đề cập đến ba vấn đề:

• Tóm tắt đề bài rõ ràng

• Thuật toán tốt

• Code demo (nếu có)

Khi áp dụng tài liệu này vào giảng dạy, tôi thường bỏ phần “code demo” để không

“làm hỏng học sinh”, chỉ phát đề cho học sinh Với mỗi bài tập, sau khi học sinhnghiên cứu và đề xuất ý tưởng (hoặc code nộp mà chưa AC), tôi dẫn dắt, đưa ra giảithuật của bài toán đó để học sinh “ngấm” bài toán hơn Dần dần học sinh nắm được

tư tưởng Interval Tree và ứng dụng linh động vào các bài toán khác

Tôi cũng xin trích dẫn các tài liệu tôi tham khảo để biên tập thành chuyên đề này:

https://doraemonvodanh.wordpress.com/category/thuat-toan/segment-tree/bai-• Quyển: Một số vấn đề chú ý môn Tin học – Nhóm tác giả của Đại học Vinh

Trang 2

Ứng dụng Interval Tree để giải các bài toán sau:

Bài 1 Phần tử thứ K http://vn.spoj.com/problems/YPKTH/

Cho dãy số A có N phần tử nguyên phân biệt

Cho Q truy vấn, mỗi truy vấn có dạng: L R K

Yêu cầu: mỗi truy vấn xuất ra phần tử lớn thứ K sau khi sắp xếp các phần tử AL, AL+1,

- Dòng đầu tiên chứa số N

- Dòng tiếp theo chứa N số A1, A2, …, AN

- Dòng tiếp theo chứa số Q

- Q dòng tiếp theo, mỗi dòng chứa 3 số L, R, K

Trang 3

THUẬT TOÁN :

Dùng Segment Tree với mỗi nút lưu lại dãy con từ l->r đã được sort Dùng vector chomỗi nút để giảm bộ nhớ: mỗi nút sẽ xuất hiện logN lần trên cây, do đó bộ nhớ làNlogN Có thể tạo cây trong O(NlogN), mỗi lần hợp hai nút con lại ta trộn hai đoạncon trong O(n+m) với n, m là kích thước của hai đoạn con

Với mỗi truy vấn ta làm như sau: Xét các giá trị (gọi là res) có trong dãy bằng cáchchặt nhị phân, (nút 1 thực chất đã sort dãy tăng dần nên có thể chặt nhị phân trên nút1), đếm xem trong đoạn l r có bao nhiêu phần tử nhỏ hơn nó, nếu nhỏ hơn k tức làphải tìm số lớn hơn nữa và tương tự Với mỗi lần truy vấn l r thì ta lại chặt nhị phânnhững nút nằm trong đoạn l r để tìm phần tử lớn nhất ≤ res đồng thời kiểm tra xemres có mặt cũng như đếm số lượng phần tử nhỏ hơn res (Chú ý là các phần tử là phânbiệt) Điều kiện để res là nghiệm chính là cnt == k-1 (cnt là số lượng số < res) và tìmthấy res trong đoạn l r

Code demo: http://ideone.com/GTScHq

while (i < u.size()) ans.pb(u[i++]);

void get(int node, int l, int r) {

if (r < L or R < l) return ;

if (L ≤ l and r ≤ R) { int i = 0, j = t[node].size()-1, pos

= -1;

while (i ≤ j) { int mid = (i+j)/2;

if (t[node][mid] ≤ res) { pos = mid;

i = mid+1;

} else j = mid-1;

int mid = (l+r)/2;

get(node*2,l,mid);

get(node*2+1,mid+1,r);

}

Trang 4

while (j < v.size()) ans.pb(v[j++]);

//freopen("YPKTH.INP","r",stdin); //freopen("YPKTH.OUT","w",stdout); scanf("%d", &n);

for (int i=1;i≤n;i++) scanf("%d",

&a[i]);

build(1,1,n);

scanf("%d", &q);

while (q ) { scanf("%d%d%d", &L,&R,&k); int l = 0, r = t[1].size()-1;

while (l ≤ r) { int mid = (l+r)/2;

}

Bài 2 Đoạn con có tổng lớn nhất http://vn.spoj.com/problems/GSS/

Cho dãy số a[1], a[2], , a[n] (|a[i]| ≤ 15000, n ≤ 50000)

Hàm q(x, y) = max { tổng(a[i]+a[i+1]+ +a[j]), x ≤ i ≤ j ≤y }

Cho m câu hỏi dạng x, y (1 ≤ x ≤ y ≤ n), (m ≤ 50000) -> hãy tính các q(x, y)

Input

- Dòng đầu là n

- Dòng thứ hai là dãy a

- Dòng thứ 3 là m

Trang 5

- m dòng tiếp theo mỗi dòng là 1 cặp số x, y.

1 2Output:

2

Thuật toán: Sử dụng Segment Tree

Một nút lưu các giá trị :

sum : tổng đoạn pre : tổng lớn nhất của đoạn tiền tốsuf : tổng lớn nhất của đoạn hậu tố ans : tổng lớn nhất của đoạn

Công thức hợp hai nút trên cây như sau :

res.sum = l.sum + r.sum;

res.pre = max(l.pre, l.sum + r.pre);

res.suf = max(r.suf, r.sum + l.suf);

Thời gian chạy:

Trang 6

res.ans = max(l.ans, r.ans, l.suf + r.pre);

typedef double real;

const int base = 1000000007;

const int oo = INT_MAX;

const ll ooo = LONG_LONG_MAX;

const real pi = acos(-1.0);

#define readln scanf("\n")

#define writeln printf("\n")

return ; }

if (L > mid) return query(k*2+1, mid+1, r, L, R);

return combine ( query(k*2, l, mid, L, mid), query(k*2+1, mid+1, r, mid+1, R) );

}

int main() {

//openf;

scanf("%d", &n);

Trang 7

data t[maxt];

data combine (data l, data r) {

data res;

res.sum = l.sum + r.sum;

res.pre = max(l.pre, l.sum + r.pre);

res.suf = max(r.suf, r.sum + l.suf);

res.ans = max( max(l.ans, r.ans), l.suf + r.pre);

Bài 3 Diện tích hình chữ nhật - http://vn.spoj.com/problems/AREA/

Trên mặt phẳng toạ độ người ta vẽ ra N hình chữ nhật Hãy tính diện tích che phủ bởi

N hình chữ nhật này, biết rằng N hình chữ nhật này song song với 2 trục Ox và Oy

Input

Dòng 1 : Số nguyên N ( 1 ≤ N ≤ 10000 )

N dòng tiếp theo, mỗi dòng gồm 4 số nguyên x1, y1, x2, y2tương ứng là toạ độ góc trái dưới và góc phải trên của hìnhchữ nhật thứ i.( 0 ≤ x1 ≤ x2 ≤ 30000, 0 ≤ y1 ≤ y2 ≤ 30000 )

Trang 8

Thuật toán:

Sử dụng Segment Tree (IT)

Chuyển dữ liệu đề cho sang một dãy tọa độ x, mỗi x có lưu lại y1 và y2 tương ứng hàng giới hạn dưới và trên, đồng thời lưu lại type là -1 hay 1 tương ứng là cạnh đóng

hay mở Sau đó sort lại mảng này theo x

Mục đích của cách xử lí trên là tại mỗi khoảng từ xi -> xi+1 xét trên dãy đã sort ta đitính phần diện tích được bao phủ bởi các y1 và y2 Lúc này ta dùng Segment Tree,

mỗi nút lưu lại cnt là số lượng đoạn phủ và len là tổng chiều dài.

Code demo: http://ideone.com/tGGNUI

if (type == 1) // them hcn nen bao phu

ca canh nay t[k].len = (r-l+1); // chang han l = 5, r = 8 thi t[k].len = 4 do (5,6,7,8)

else { // truong hop xoa thi phai lay thong tin tu node con

if (t[k].cnt == 0) t[k].len = t[k*2].len + t[k*2+1].len;

} return ; }

int mid = (l+r)/2;

update(k*2,l,mid,L,R,type);

update(k*2+1,mid+1,r,L,R,type);

if (t[k].cnt == 0) t[k].len = t[k*2].len + t[k*2+1].len;

}

int main() {

Trang 9

Tree t[5*N]; // luu ve phuong dien do

dai, khong phai toa do (*)

a[i].y1 = a[i+n].y1 = y1;

a[i].y2 = a[i+n].y2 = y2;

for (int i=1;i≤n;i++) { //cout << (a[i].x-x0)*(t[1].len) << endl;

res += (a[i].x - x0) * (t[1].len);

x0 = a[i].x;

update(1,0,N, a[i].y1, a[i].y2-1, a[i].type); // a[i].y2-1 la do (*)

} cout << res;

Hi (Các điểm số đều là số nguyên không âm và không vượt quá 100000) Lập trìnhviên i được coi là giỏi hơn lập trình viên j khi và chỉ khi cả 2 điểm số của lập trìnhviên i đều lớn hơn hoặc bằng điểm số tương ứng của lập trình viên j, trong đó có ítnhất 1 điểm số phải lớn hơn Hãy tính xem với mỗi lập trình viên i thì có bao nhiêulập trình viên mà i giỏi hơn

Input

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

N dòng tiếp theo, dòng thứ i+1 chứa 2 số nguyên Ai và Hi

Trang 11

+ Tạo một cây IT là mảng t[k] lưu thông tin của nút k quản lí đoạn l r (xét trên sốđiểm) và một mảng f lưu nghiệm của bài toán, tức là f[i] là số lượng những thằng tồihơn thằng i sau khi đã sort lại.

+ Tạo thêm biến m lưu số điểm d2 lớn nhất vì theo như tài liệu d1 ta đã sort lại rồi thìkhông cần quan tâm điểm lớn hay nhỏ

+ Sau khi nhập dữ liệu thì sort lại dãy

+ Tạo biến q là vị trí đang duyệt trên dãy a ( từ 1 -> n, lần lượt xét các đoạn có cùngd1) Khi q > n thì xuất nghiệm ra, halt luôn trước hết q = 0

+ Tạo biến L và R lưu đoạn xét nói trên Khi đó ban đầu L = q và R = q lần lượt tăng

R lên đến khi d1 thay đổi Xong thì xét đoạn L R mới tìm được

+ Khởi tạo f[L] = 0 Tạo f

+ Sau đó lấy giá trị cho f trên cây t (nhớ là xét trên số điểm từ 0 m, đoạn ban đầufindans) : điểm đang xét là x Ta sẽ tìm những đoạn mà r ≤ x để lấy ans còn nhữngđoạn nào mà x < l ( điểm thấp hơn ) thì ta bỏ qua Trường hợp còn lại thì chia ra 2đoạn mà làm như các bài IT cơ bản

+ Lấy giá trị xong thì cập nhật lại cây t: bỏ qua những đoạn x không thuộc, tăng sốlượng nút này lên, nếu l = r thì thôi chia hai đoạn để it

if (u.d1 < v.d1) return true;

if (u.d1 == v.d1 and u.d2 < v.d2) return true; return false;

}

Trang 12

sort(a+1,a+n+1,compare);

q = 0;

while (1 == 1) { q++;

if (q > n) { for (int i=1;i≤n;i++) res[a[i].vt] = f[i];

for (int i=1;i≤n;i++) printf("%d\n",res[i]); return 0;

for (int i=L+1;i≤R;i++)

if (a[i].d2 ≤ a[i-1].d2) f[i] = f[i-1];

q = R;

} return 0;

}

Bài 5 http://vn.spoj.com/problems/QMAX2/

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í v lê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ử có giá trị lớn nhất thuộc đoạn[u, v]

Input

Trang 13

– n: số phần tử của dãy (n ≤ 50000).

– m: số lượng biến đổi và câu hỏi (m ≤ 100000)

+) biến đổi có dạng: 0 x y value

sẽ đi đến tất cả các đoạn có trong đoạn x y và độ phức tạp tỷ lệ với n)

Nếu như vậy, ta cần có 1 mảng phụ T để lưu lại giá trị cần tăng lên cho tất cả cácphần tử của đoạn này, khi ta truy vấn đến 1 nút k đồng thời ta tăng T[k] lên choT[k*2],

Trang 14

T[k*2+1] và F[k] (do T[k] là giá trị cần tăng lên cho tất cả những phần tử của đoạnnút k quản lí, nên các nút con của nó cũng phải tăng lên T[k])

Sau khi đã cập nhật cho mảng F và các nút con, ta gán lại T[k]=0 để tránh trường hợpcộng lại nhiều lần Nếu đã đến đoạn nằm gọn trong đoạn x y thì ta tăng mỗi biếnF[k], T[k*2], T[k*2+1] lên v Khi muốn lấy kết quả, ta vẫn làm như bài QMAX,nhưng đến mỗi nút con, ta vẫn cần thực hiện tăng giá trị T[k] cho T[k*2], T[k*2+1]

và F[k] để lấy được kết quả Tức là khi đến được 1 nút nào đó (trong bất kì thao tácnào, cập nhật hay lấy kết quả) thì đều thực hiện như vậy Điều này đảm bảo là ta cóthể lấy được giá trị chính xác, đây là kiểu cây IT có thông tin truyền từ nút cha xuốngnút con Các bạn có thể tham khảo chương trình để thấy rõ hơn

t[k] la max cua doan nut k quan li

f[k] la mang phu luu phan can tang len cua

}

Trang 15

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

Trang 16

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ì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, 1câ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à maxt và mint

Khi muốn tìm kết quả thì dựa vào mảng Maxt để tìm GTLN trên đoạn A B, dùngmảng Mint để tìm GTNN trên đoạn A B, việc này làm tương tự như trong ví dụ củabài viết giới thiệu về Interval tree phía trên Chú ý là khi tìm max hay tìm min ta đềuphải đi đến những nút giống nhau (do đi đến những nút nào thì chỉ phụ thuộc A và B)nên mỗi lần tìm chỉ cần gọi chung 1 thủ tục

Trang 17

void update(long k,long l,long r) {

if (l == r) tmin[k] = tmax[k] = a[l];

scanf("%li%li",&n,&q);

for (long i=1;i≤n;i++) scanf("%li",&a[i]); update(1,1,n);

for (long Q=1;Q≤q;Q++) { scanf("%li%li",&L,&R);

}

Bài 7 http://vn.spoj.com/problems/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í v lê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ử có giá trị lớn nhất thuộc đoạn[u, v]

Giới hạn

• n, m, q ≤ 50000

• k > 0

Trang 18

• Giá trị của một phần tử luôn không vượt quá 231-1

Vấn đề bây giờ là xây dựng dãy số sau m phép biến đổi

Ta có thể sử dụng 1 kĩ thuật đơn giản những rất hiệu quả như sau

Giả sử mảng ta cần có là mảng A[0 n+1], lúc đầu A[i]=0 với mọi i

Mỗi yêu cầu u,v,k tức là tăng các phần tử từ vị trí u đến vị trí v lên k đơn vị, ta làmnhư sau: A[u]:=A[u]+k;A[v+1]:=A[v+1]-k;

Sau khi đọc xong m phép biến đổi và làm như trên, cuối cùng là tính mảng A:

Trang 19

For i:=1 to n do

A[i]:=A[i]+A[i-1];Các bạn có thể tự chứng minh tính đúng đắn hay có thể viết đoạn chương trình này ra

và kiểm nghiệm lại Như vậy ta đã có thể giải quyết trọn vẹn bài toán

else { long m = (l+r)/2;

findres(k*2,l,m);

findres(k*2+1,m+1,r);

} } } int main() {

update(1,1,n);

scanf("%li",&q);

for (long Q=1;Q≤q;Q++) { scanf("%li%li",&L,&R);

res = 0;

findres(1,1,n);

printf("%li\n",res);

} return 0;}

Bài 8 Dãy ngoặc http://laptrinh.ntu.edu.vn/Problem/Details/3270/?contestid=9

Khái niệm dãy ngoặc đúng được định nghĩa dưới dạng đệ quy như sau:

1 () là dãy ngoặc đúng

2 C là dãy ngoặc đúng nếu C = (A) hay C = AB với A, B là các dãy ngoặc đúng.

Ví dụ dãy ngoặc đúng: (), (()), ()(), (())()

Ví dụ dãy ngoặc sai: )(, ((((, ()((, )))), )()(

Cho trước một dãy ngoặc bất kỳ gồm n dấu ngoặc được đánh số từ 1 đến n Có m câu hỏi, mỗi câu gồm hai số nguyên 1 ≤ p ≤ r ≤ n, yêu cầu xác định xem dãy ngoặc

Trang 20

con từ vị trí p đến vị trí r có phải là dãy ngoặc đúng hay không Hãy cho biết kết quả

của m câu hỏi trên

Dữ liệu nhập:

– Dòng đầu tiên là hai số nguyên n, m cách nhau một khoảng trắng (1 ≤ n, m ≤ 105)

– Dòng thứ hai là dãy ngoặc ban đầu gồm n dấu ngoặc ‘(‘ hay ‘)’.

– Trong m dòng tiếp theo, tại dòng thứ i là hai số pi và ri của câu hỏi thứ i tương ứng,hai số cách nhau một khoảng trắng (1 ≤ pi ≤ ri ≤ n)

Dữ liệu xuất:

– Gồm m dòng ứng với m câu hỏi, nếu dãy ngoặc con tương ứng là dãy ngoặc đúng,

in ra YES, nếu dãy ngoặc con tương ứng là không phải dãy ngoặc đúng, in ra NO

if ((v-u+1) & 1) return 0;

int y = (u == 1 ? 0 : get(1, 1, n, u-1, u-1));

Trang 21

void update(int k, int x, int y, int d, int c, int v)

for (int i=1; i≤n; i++) {

scanf("%c", &ch);

x += (ch == '(') ? 1 : -1;

update(1, 1, n, i, i, x);

} while (m ) {

scanf("%d %d\n", &x, &y);

printf("%s\n", check(x,y) ? "YES" :

"NO");

} }

Bài 9 Búp bê Nga

Búp bê Nga(Búp bê lồng nhau, Búp bê làm tổ, …) là một loại búp bê đặc trưng củaNga Thật ra đó là một bộ gồm những búp bê rỗng ruột có kích thước từ lớn đến nhỏ.Con búp bê nhỏ nhất sẽ được chứa đựng trong lòng con búp bê lớn hơn nó một chút,đến lượt mình con búp bê lớn được chứa trong một con búp bê khác lớn hơn, và cứthế cho đến con lớn nhất sẽ chứa tất cả những con búp bê còn lại trong bộ

Đầu tiên yenthanh132 sắp xếp N con búp bê của anh ta từ trái sang phải theo một thứ

tự bất kì, con thứ i có kích thước ai (1 ≤ ai ≤ M) Sau đó anh ta yêu cầu bạn tạokhoảng trống ở đầu mút trái, để làm được điều đó, bạn cần xếp những con búp bê cókích thước nhỏ hơn ở bên trái đặt vào những con búp bê có kích thước lớn hơn bênphải, tuy nhiên mỗi con búp bê lớn hơn chỉ chứa đúng 1 con búp bê nhỏ hơn nó Bạnchỉ được quyền dùng 3 thao tác: Lấy một con búp bê ở bên trái lên, di chuyển nó sangphải và đặt vào bên trong một con búp bê lớn hơn Bạn cần tìm cách sắp xếp sao chokhoảng trống ở đầu mút trái là lớn nhất

Ngày đăng: 09/03/2021, 13:57

TỪ KHÓA LIÊN QUAN

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

w