You are on page 1of 24

Fenwick tree - Cây chỉ số nhị phân (Binary Indexed Tree)

Contents

 Bài toán
 Định nghĩa BIT
 Thao tác truy xuất
 Thao tác cập nhật
 Một số bài tập áp dụng (và code giải)

Fenwick Tree, hay còn gọi là cây chỉ số nhị phân (Binary Indexed Tree - BIT), là một cấu
trúc dữ liệu tối ưu cho việc cập nhật giá trị một phần tử và tìm tổng, min/max giữa 2 vị
trí bất kì trong mảng. Độ phức tạp cho mỗi lần cập nhật, truy xuất
là O(logN)O(log⁡N) với N là độ dài dãy cần quản lý. Ngoài thao tác tính tổng, tìm
min/max thì BIT còn có thể sử dụng được cho nhiều thao thác khác nữa.

BIT là một cây, tuy nhiên trong phạm vi bài viết này sẽ không đề cập tới khái niệm gốc
hoặc chứng minh tính đúng đắn của nó, mà chỉ dừng lại ở mức kiến thức đủ để cài đặt
và sử dụng trong thực tế.

Bài toán
Cho dãy số A có N phần tử, giá trị ban đầu của các phần tử bằng 0. Có 2 loại truy vấn
cần thực hiện:

 Tăng A(i)A(i) lên một đơn vị


 Tính tổng của mảng từ A(1)A(1) đến A(i)A(i).

Nếu sử dụng cách tính tổng như bình thường thì thao tác cập nhật có độ phức tạp
là O(1)O(1), còn thao tác tính tổng có độ phức tạp O(N)O(N).

Nếu sử dụng BIT cho bài này thì cả 2 thao tác có chung độ phức tạp
là O(logN)O(log⁡N).

Định nghĩa BIT


Xem BIT như một mảng F có N phần tử, đánh số từ 1. F(i)F(i) lưu tổng của i & (-
i) phần tử, bắt đầu từ A(i)A(i) ngược về A(1)A(1). Tức là F(i)F(i) lưu tổng từ A(i-(i&-
i)+1) đến A(i).
Giải thích phép tính i & (-i):

i & (-i) sẽ trả về bit đầu tiên khác 0 của i, ví dụ i=20i=20, có biểu diễn nhị phân
là 1010010100, thì i & (-i) sẽ có biểu diễn nhị phân là 100100, tức là 20 & (-20) = 4.

Ví dụ mảng A có 10 phần tử và cây BIT tương ứng:

  1 2 3 4 5 6 7 8 9 10
A 3 2 1 5 3 6 2 0 7 1
F 3 5 1 11 3 9 2 22 7 8

Thao tác truy xuất


Để tính tổng từ A(1)A(1) đến A(i)A(i), ta lặp như sau:

result = 0
while i >= 1:
result = result + F[i]
i = i - (i & -i)

Có thể thấy, sau mỗi lần lặp thì một bit 1 trong ii sẽ bị loại bỏ, vì thế số lần lặp là số
lượng bit 1 của ii, dẫn đến độ phức tạp là O(logN)O(log⁡N).

Cách trên cho phép tính tổng từ đầu dãy đến A(i)A(i), nếu muốn tính tổng trên một
đoạn từ A(j)A(j) đến A(i)A(i) thì phải thay đổi thao tác trên một ít:

result = 0
while i >= j:
if i - (i & -i) >= j:
result = result + F[i]
i = i - (i & -i)
else:
result = result + A[i]
i = i - 1

Thao tác cập nhật


Trong truy vấn cập nhật, ta cần tăng giá trị của A(i)A(i) lên 1, vì vậy ta cũng cần phải
tăng các giá trị F(j)F(j) mà trong dãy tổng của F(j)F(j) có chứa A(i)A(i). Để làm được
điều này, chỉ cần làm ngược lại với thao tác truy xuất bên trên, tức là i = i + (i & -i).
Đoạn code cập nhật như sau:

while i <= n:
F[i] = F[i] + 1
i = i + (i & -i)

NKINV - Dãy nghịch thế


Cho một dãy số a1.. aN. Một nghịch thế là một cặp số u, v sao cho u < v và a u > av. Nhiệm vụ của bạn
là đếm số nghịch thế.

Dữ liệu
 Dòng đầu ghi số nguyên dương N.
 N dòng sau mỗi dòng ghi một số ai ( 1 ≤ i ≤ N ).

Kết qủa
Ghi trên một dòng số M duy nhất là số nghịch thế.

Giới hạn
 1 ≤ N ≤ 60000
 1 ≤ ai ≤ 60000

Ví dụ
Dữ liệu:
3
3
1
2

Kết qủa
2

Gọi C[x]C[x] là số lượng giá trị xx đã xuất hiện. Duyệt các phần tử từ đầu dãy đến cuối
dãy, mối lần duyệt ta tính tổng từ C[x+1]C[x+1] đến cuối mảng CC rồi thêm vào kết
quả, sau đó tăng C[x]C[x] (xx là phần tử đang xét).
Để AC cần sử dụng cấu trúc Fenwick Tree để giảm độ phức tạp của phương pháp trên
xuống còn O(NlogC)O(Nlog⁡C).

Lưu ý là ta cần tính tổng từ một vị trí đến cuỗi dãy, ngược lại với phiên bản trong bài
viết trên.

#include <iostream>
#include <vector>
using namespace std;

#define LL long long

struct Fenwick {
int n;
vector<LL> f;
Fenwick(int n): n(n), f(n+1, 0) {}
void set(int i) {
for (; i>=1; i -= i&(-i)) f[i]++;
}
LL get(int i) {
LL result = 0;
for (; i<=n; i += i&(-i)) result += f[i];
return result;
}
};

int main() {
ios::sync_with_stdio(false); cin.tie(0);

int n; cin >> n;


Fenwick f(60000);
LL result = 0;
while (n--) {
LL x; cin >> x;
result += f.get(x+1);
f.set(x);
}
cout << result;

return 0;
}
NKLINEUP - Xếp hàng
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 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ữa 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ứ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ạn
cá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.

Ví dụ
Dữ liệu:
6 3
1
7
3
4
2
5
1 5
4 6
2 2

Kết qủa
6
3
0

 Submit solution!

#include <iostream>
#include <vector>
using namespace std;

void minimize(int &a, int b) {


if (a > b) a = b;
}
void maximize(int &a, int b) {
if (a < b) a = b;
}
typedef void (*func)(int &, int);

struct Fenwick {
int n, init;
vector<int> f, a;
func update;

Fenwick(int n, int val, func func):


n(n), init(val),
f(n+1, val), a(n+1),
update(func) {}

int get(int l, int r) {


int result = init;
while (l <= r) {
if (r-(r&-r) >= l) update(result, f[r]), r -= r&-r;
else update(result, a[r]), r -= 1;
}
return result;
}
void set(int i, int x) {
a[i] = x;
for (; i<=n; i += i&-i) update(f[i], x);
}
};

int main() {
ios::sync_with_stdio(false); cin.tie(0);

int n, q; cin >> n >> q;


Fenwick fmax(n, 0, maximize);
Fenwick fmin(n, 1e8, minimize);

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


int x; cin >> x;
fmax.set(i, x);
fmin.set(i, x);
}

while (q--) {
int l, r; cin >> l >> r;
cout << fmax.get(l, r) - fmin.get(l, r) << endl;
}

return 0;
}

7.      Tính tổng các giá trị được tích luỹ từ nút i đến nút j (kể cả tích luỹ trên nút i và trên
nút j)như thế nào? (trả lời yêu cầu 2).
Rõ ràng tổng các giá trị được tích luỹ từ nút i đến nút j (i<=j) bằng tổng tích luỹ đến nút j trừ đi các
giá trị đã tích luỹ trên các nút trước i (đó là tổng các giá trị được tích luỹ trên nút i-1). Ta có hàm tính
tổng này như sau:

int sum(int i, int j) {


       return calcul_c(j)- calcul_c(i-1);

8.      Bài toán ứng dụng.
Có dãy N tấm bìa. Các tấm bìa đều úp mặt trên bàn. Bạn có hai câu chất vấn cần thực hiện:

1. T i j là đề nghị quay ngược các tấm bìa từ chỉ số i đến chỉ số j (kể cả i và j) tấm nào đang úp
thì thành ngửa, tấm nào đang ngửa thì úp xuống
2. Q i yêu cầu trả lời là 0 nếu tấm bìa i là úp, ngược lại trả lời là 1 nếu tấm bìa i ngửa.
Input. Tệp gồm nhiều dòng, mỗi dòng là một chất vấn thuộc hai loại trên
Output. Gồm một số dòng, mỗi dòng là một trả lời cho chất vấn Q(i), theo đúng thứ tự chất vấn
trong input.
Ví dụ.

input output
T 1 4T 3 6T 2 5Q 1Q 2 10111

Q3 1

Q4 0

T23 1

Q1 0

Q2 1

Q3 0

Q4

Q5

Q6

Q7
Hướng dẫn. Ban đầu các bìa chưa chịu tác động nào nên coi tần số các nút đều bằng 0. Giá trị tích
luỹ tổng các tần số lên các nút cũng bằng 0 tại mọi nút. Thực hiện chất vấn T(i, j): với mỗi nút k nằm
giữa i và j (kể cả i và j) cần cập nhật tree[k] thêm 1 (vì thêm một lần lật quân k). Câu trả lời cho Q(i)
chính là f[i] mod 2 (vì ban đầu i úp, sau số chẵn lần lật thì vẫn úp). Lời giải cho mỗi câu chất vấn
được thực hiện trong thời gian O(logN).
II) Cấu trúc Binary indexed trees 2-D.
Cấu trúc Binary Indexed Trees tổ chức trên  mảng một chiều có thể tổng quát hoá thành cấu trúc
Binary Indexed Trees trên mảng nhiều chiều. Sau đây là cấu trúc Binary Indexed Trees trên mảng hai
chiều (2-Dimensions).

Xét mảng hai chiều kích thước A[1..M, 1..N] (M dòng, N cột), giả sử đánh số hàng từ 1 đến M và
đánh số cột từ 1 đến N. Ta xây dựng tập S1 gồm M cây BIT (Binary indexed trees), mỗi BIT dùng xử
lý thông tin một dòng của mảng A  (mỗi BIT có N nút, mỗi nút i gán giá trị là tổng tích luỹ từ ô 1 đến
ô i của dòng). Đồng thời xây dựng tập S2 gồm N cây BIT: cây BIT thứ nhất của S2 quản lý các nút
thứ nhất của M cây BIT thuộc S1, cây BIT thứ hai của S2 quản lý các nút thứ hai của M cây BIT
thuộc S1, …, cây BIT thứ N của S2 quản lý các nút thứ N của M cây BIT thuộc S1.
1.      Cập nhật nút (x;y)
Khi cần cập nhật thêm giá trị amount vào ô (x;y) thuộc dòng y cột x của mảng A ta cập nhật lại các ô
là tiền bối của (x;y) như sau:
1. a) Trên cây BIT thứ y của tập S1, tìm các nút xtiền bối của nút x
2. b) Với mỗi cây BIT thứ xtiền bối của tập S2, tìm các nút ytiền bối của y.
Cập nhật ô (xtiền bối; ytiền bối).
void update (int x, int y, int amount){
  int ix, jy;
for( ix=x; ix < maxM; ix += (ix & (-ix)) ) // Tìm đến các tiền bối của x 
for( jy=y; jy < maxN; jy += (jy & (-jy)) // Tìm đến các tiền bối của y
     A[jy][ix] += amount; // Cập nhật các ô tiền bối của ô (x,y) dòng y, cột x
}
Hai hình sau đây lần lượt minh hoạ cập nhật ô (1; 1) và ô (2; 5) dòng 5, cột 2

Cập nhật ô [1; 1] Cập nhật ô [5; 2]

Chú ý : Trong mỗi cây BIT, nút 8 là cha của nút 4, 6 và 7 ; nút 4 là cha của nút 2 và 3; nút 2 là cha
của nút 1; nút 6 là cha của nút 5

2.      Tính tổng các ô trong hình chữ nhật (x1, y1,x2,y2)
Để trả về tổng các ô trong hình chữ nhật có góc trái-dưới là ô (x 1;  y1) và góc phải-trên là ô (x2; y2) ta
thực hiện quá trình ngược lại quá trình cập nhật:
1. a) Trên cây BIT thứ x2 của S1, tìm các jy là các hậu duệ của y2, jy>y1
2. b) Trên cây BIT thứ jy của S2, tìm các ix là các hậu duệ của x 2, ix>x1
3. c) Giảm tổng tích luỹ trên (x2; y2) một lượng là tổng tích luỹ trên các ô hậu duệ (ix; jy).
Hàm tính tổng các ô trong hình chữ nhật có góc trái-dưới là ô (x 1; y1) và góc phải-trên là ô (x2; y2)
giới thiệu trong ví dụ dưới đây:
3.      Ví dụ.  Giải bài tập MATSUM – Matrix Summation
Cho ma trận A (N × N) các số. BuggyD là nhà phân tích ma trận và anh ấy muốn biết tổng các số
trong một ma trận con bất kỳ của A, do đó muốn có một hệ thống cho kết quả ứng với các truy vấn
này. Hệ thống cũng đáp ứng ngay cả khi ma trận biến động chấp nhận các giá trị mới trên các ô của
ma trận. Giả sử ban đầu các ô của ma trận đều bằng 0. Bạn hãy xây dựng hệ thống cho BuggyD. Đọc
khuôn dạng input cho chi tiết dưới đây

Input
Dòng đầu tiên của input chứa số nguyên t, là số các test. Tiếp theo là t test. Dòng đầu của mỗi test
chứa số nguyên N (1 <= N <= 1024), là chiều rộng ma trận. Tiếp theo là danh sách các lệnh, mỗi
lệnh có một trong 3 dạng sau:
SET x y num – Đặt giá trị tại ô (x, y) (0 <= x, y < N) thành num
SUM x1 y1 x2 y2 – Tìm và xuất ra tổng các số trong hình chữ nhật từ ô (x1, y1) đến ô (x2, y2), kể cả
chúng, x1 <= x2 và y1 <= y2, và kết quả không vượt quá số nguyên 32 bit.

END – Kết thúc một test.

Output
Với mỗi test, output trên một dòng cho mỗi lệnh SUM. Để một dòng trống sau mỗi test.

Ví dụ

Input:

1
4
SET 0 0 1
SUM 0 0 3 3
SET 2 2 12
SUM 2 2 2 2
SUM 2 2 3 3
SUM 0 0 2 2
END
Output
1
12
12
13
Chương trình.
#include <iostream>
#include <fstream>
using namespace std;
#define LL long long
LL tree[1050][1050];
void update(int x,int y,int val,int MAX){
    while(x<=MAX)    {
        int ty = y;
        while(ty <= MAX)        {
            tree[x][ty] += val;
            ty += (ty & -ty);
        }
        x += (x & -x);
    }
}
LL read(int x,int y){  //Tổng các số trong hcn từ (0,0) đến (x-1, y-1)
    LL sum = 0;
    while( x )    {
        int ty = y;
        while( ty )        {
            sum += tree[x][ty];
            ty -= (ty & -ty);
        }
        x -= (x & -x);
    }
    return sum;
}
int main(){
    int t;
    freopen(“matsum.inp”,”r”,stdin);
    freopen(“matsum.out”,”w”,stdout);
    scanf(“%d”,&t);
    while(t–) {
        int n;
        scanf(“%d”,&n);
        memset(tree,0,sizeof tree);
        while(1) {
            char s[10];
            scanf(“%s”,s);
            if(s[1] == ‘E’){ //SET
                int x,y,val;
                scanf(” %d%d%d”,&x,&y,&val);//Yêu cầu đặt giá trị tại ô (x,y)thành val
                                       //Giá trị đã có tại ô (x,y)
                LL p_val=read(x+1,y+1)+read(x,y)-read(x+1,y)-read(x,y+1);
                update(x+1,y+1,val-p_val,n+9); // Thêm val-p_val vào ô (x,y)
            }
            else if(s[1] == ‘U’){ //SUM
                LL sum = 0;
                int x1,y1,x,y;
                scanf(” %d%d%d%d”,&x,&y,&x1,&y1); //Xuất ra tổngtrong hcn(x,y,x1,y1)
                sum=read(x1+1,y1+1)+read(x,y)-read(x,y1+1)-read(x1+1,y);
                printf(“%lld\n”,sum);
            }
            else{
                //printf(“\n”);
                break;
            }
        }
        printf(“\n”);
    }
    return 0;
}
Ghi chú.

read(x1+1,y1+1): cho tổng các số trong hcn OEBF


read(x,y): cho tổng các số trong hcn OHDK
read(x,y1+1): cho tổng các số trong hcn OEAK
read(x1+1,y); cho tổng các số trong hcn OHCF
Do đó: read(x1+1,y1+1)+read(x,y)-read(x,y1+1)-read(x1+1,y);

cho tổng các số trong hcn ABCD


4. Bài toán ứng dụng.
Giả thiết một thế hệ thứ 4 điện thoại di động (mobile phone) có các trạm làm việc nằm trong vùng
Tampere hoạt động như sau: Vùng hoạt động này được chia theo lưới ô vuông. Các ô vuông tạo
thành một ma trận SxS với các hàng và cột được đánh số từ 0 đến S-1. Mỗi ô vuông chứa một trạm
làm việc. Số lượng các điện thoại đang hoạt động trong một ô vuông sẽ bị thay đổi khi người sử dụng
điện thoại di chuyển từ ô này sang ô khác hoặc điện thoại chuyển chế độ bật/tắt. Theo thời gian, mỗi
trạm làm việc sẽ báo cáo sự thay đổi số lượng điện thoại di động đang hoạt động trong khu vực kiểm
soát của mình.

Hãy viết chương trình nhận các báo cáo đó và trả lời được các yêu cầu về tổng số điện thoại di động
đang hoạt động trong một vùng không gian hình vuông cho trước. Hãy viết chương trình  tiếp nhận
các báo cáo này và trả lời các yêu câu cho biết tổng số điện thoại đang hoạt động trong một hình chữ
nhật của vùng.

Input: Dữ liệu vào đọc từ tệp input chuẩn như các số nguyên và trả lời các chất vấn ghi vào output
chuẩn như các số nguyên. Dữ liệu vào đã được mã hoá như sau: Mỗi dữ liệu vào trên một dòng riêng
gồm một số nguyên chỉ dẫn và một số tham số nguyên như bảng sau:
Số nguyên chỉ Các số nguyên Ý nghĩa
dẫn tham số

0 S Khởi trị ma trận kích thước SS. Chỉ dẫn này chỉ cho một lần
và là chỉ dẫn đầu tiên

1 XYA Thêm A điện thoại hoạt động trong trạm (x,y)

2 LBRT Hỏi tổng số điện thoại đang hoạt động trong các trạm (x,y)
với L≤x≤R, B≤y≤T.

3 Dừng chương trình. Chỉ dẫn này chỉ cho một lần và là chỉ
dẫn cuối cùng.

Các giá trị luôn trong phạm vi, không cần kiểm tra. Riêng A có thể âm, khi đó không giảm giá trị
trạm xuồng dưới 0. Các chỉ số bắt đầu từ 0 ví dụ bảng 44 thì có 0 x3, 0y3. Chương trình của bạn sẽ
không trả lời cho bất kì chỉ dẫn nào khác 2. Nếu chỉ dẫn là 2 thì chương trình của bạn cần trả lời chất
vấn bằng ghi trả lời trên đúng một dòng chứa số nguyên trên tệp output.

Stdin(input Stdout(output Giải thích


chuẩn) chuẩn)

04 Khởi trị bảng 4´4

1123 Cập nhật bảng tại ô (1,2) với +3

20022 Chất vấn tổng trong hình chữ nhật gồm các ô (x,y) mà
0≤x≤2; 0≤y≤2

3 Trả lời chất vấn

1112 Cập nhật bảng tại ô (1,1) với +2

1 1 2 -1 Cập nhật bảng tại ô (1,2) với -1

21123 Chất vấn tổng trong hình chữ nhật gồm các ô (x,y) mà
1≤x≤2; 1≤y≤3

4 Trả lời chất vấn

3 Dừng chương trình

Hạn chế:

Kích thước bảng S´S 1´1 ≤ S´S ≤ 1024´1024

Giá trị mỗi ô là V tại bất kì thời điểm nào V 0 ≤ V  ≤ 215-1 (=32767)
Lượng cập nhật A -215  ≤ A  ≤ 215-1

Số lượng chỉ dẫn trong input U 3  ≤ U  ≤ 60002

Số tối đa điện thoại trong bảng M M=230

Trong 20 test input có 16 test kích thước bảng tối đa là 512.

Hướng dẫn. Xây dựng cây BIT_2D. Lưu ý, trong các cây BIT thuộc tập S1 và tập S2, các nút được
đánh số từ 1 đến N, nhưng trong bài toán này dòng và cột của mảng đánh số từ 0.
 
 
 
 
 
 
 
Advertisements
Report this ad

Report this ad

Chia sẻ:

 Twitter
 Facebook
 Google

Trả lời

BÀI & TRANG ĐƯỢC ĐÁNG CHÚ Ý

 TS. Đỗ Đức Đông


 Khớp và Cầu
 INSTR 01-2015
 Binary Indexed Tree
 SOL 01-2015
 PROB 01-2015
 05-Q H ĐỘNG
 03-SẮP XẾP
 ABOUT US
 Queue-Stack
THƯ VIỆN

Thư viện                     

SỔ BLOG

 Hướng dẫn một số bài trên codeforces


 Lưu trữ các kỳ thi Olympic toàn Nga
 Lưu trữ ICPC
 Lưu trữ lời giải một số bài Online-Judge
 Lưu trữ UVA
 Olympic Tin học Sinh viên
 Tư liệu Olympic Tin học Quốc tế
Advertisements
Report this ad

BÀI VIẾT MỚI

  Trao đổi đề thi OLP’15 (Siêu cúp)


  Trao đổi về đề thi OLP’15 (chuyên Tin)
  Bài tập tháng 12 – 2015
  Bài tập tháng 11 – 2015
  Nội dung bài tập tháng 10 – 2015
SỔ BLOG

 Hướng dẫn một số bài trên codeforces


 Lưu trữ các kỳ thi Olympic toàn Nga
 Lưu trữ ICPC
 Lưu trữ lời giải một số bài Online-Judge
 Lưu trữ UVA
 Olympic Tin học Sinh viên
 Tư liệu Olympic Tin học Quốc tế

H B T N S B C

« Th11    

  1 2
H B T N S B C

3 4 5 6 7 8 9

10 11 12 13 14 15 16

17 18 19 20 21 22 23

24 25 26 27 28 29 30

Tháng Chín 2018

Tạo một website miễn phí hoặc 1 blog với WordPress.com.


Close and accept
Privacy & Cookies: This site uses cookies. By continuing to use this website

8.      Bài toán ứng dụng.


Có dãy N tấm bìa. Các tấm bìa đều úp mặt trên bàn. Bạn có hai câu chất vấn cần thực hiện:

1. T i j là đề nghị quay ngược các tấm bìa từ chỉ số i đến chỉ số j (kể cả i và j) tấm nào đang úp
thì thành ngửa, tấm nào đang ngửa thì úp xuống
2. Q i yêu cầu trả lời là 0 nếu tấm bìa i là úp, ngược lại trả lời là 1 nếu tấm bìa i ngửa.
Input. Tệp gồm nhiều dòng, mỗi dòng là một chất vấn thuộc hai loại trên
Output. Gồm một số dòng, mỗi dòng là một trả lời cho chất vấn Q(i), theo đúng thứ tự chất
vấn trong input.
Ví dụ.

input output

T 1 4T 3 6T 2 5Q 1Q 2 10111

Q3 1

Q4 0

T23 1

Q1 0
Q2 1

Q3 0

Q4

Q5

Q6

Q7
Hướng dẫn. Ban đầu các bìa chưa chịu tác động nào nên coi tần số các nút đều bằng 0. Giá trị
tích luỹ tổng các tần số lên các nút cũng bằng 0 tại mọi nút. Thực hiện chất vấn T(i, j): với mỗi
nút k nằm giữa i và j (kể cả i và j) cần cập nhật tree[k] thêm 1 (vì thêm một lần lật quân k).
Câu trả lời cho Q(i) chính là f[i] mod 2 (vì ban đầu i úp, sau số chẵn lần lật thì vẫn úp). Lời
giải cho mỗi câu chất vấn được thực hiện trong thời gian O(logN).
II) Cấu trúc Binary indexed trees 2-D.
Cấu trúc Binary Indexed Trees tổ chức trên  mảng một chiều có thể tổng quát hoá thành cấu
trúc Binary Indexed Trees trên mảng nhiều chiều. Sau đây là cấu trúc Binary Indexed Trees
trên mảng hai chiều (2-Dimensions).

Xét mảng hai chiều kích thước A[1..M, 1..N] (M dòng, N cột), giả sử đánh số hàng từ 1 đến M
và đánh số cột từ 1 đến N. Ta xây dựng tập S1 gồm M cây BIT (Binary indexed trees), mỗi BIT
dùng xử lý thông tin một dòng của mảng A  (mỗi BIT có N nút, mỗi nút i gán giá trị là tổng
tích luỹ từ ô 1 đến ô i của dòng). Đồng thời xây dựng tập S2 gồm N cây BIT: cây BIT thứ nhất
của S2 quản lý các nút thứ nhất của M cây BIT thuộc S1, cây BIT thứ hai của S2 quản lý các
nút thứ hai của M cây BIT thuộc S1, …, cây BIT thứ N của S2 quản lý các nút thứ N của M cây
BIT thuộc S1.

1.      Cập nhật nút (x;y)


Khi cần cập nhật thêm giá trị amount vào ô (x;y) thuộc dòng y cột x của mảng A ta cập nhật lại
các ô là tiền bối của (x;y) như sau:
1. a) Trên cây BIT thứ y của tập S1, tìm các nút xtiền bối của nút x
2. b) Với mỗi cây BIT thứ xtiền bối của tập S2, tìm các nút ytiền bối của y.
Cập nhật ô (xtiền bối; ytiền bối).
void update (int x, int y, int amount){
  int ix, jy;
for( ix=x; ix < maxM; ix += (ix & (-ix)) ) // Tìm đến các tiền bối của x 
for( jy=y; jy < maxN; jy += (jy & (-jy)) // Tìm đến các tiền bối của y
     A[jy][ix] += amount; // Cập nhật các ô tiền bối của ô (x,y) dòng y, cột x
}
Hai hình sau đây lần lượt minh hoạ cập nhật ô (1; 1) và ô (2; 5) dòng 5, cột 2
Cập nhật ô [1; 1] Cập nhật ô [5; 2]

Chú ý : Trong mỗi cây BIT, nút 8 là cha của nút 4, 6 và 7 ; nút 4 là cha của nút 2 và 3; nút 2 là cha
của nút 1; nút 6 là cha của nút 5

2.      Tính tổng các ô trong hình chữ nhật (x1, y1,x2,y2)
Để trả về tổng các ô trong hình chữ nhật có góc trái-dưới là ô (x1;  y1) và góc phải-trên là ô (x2;
y2) ta thực hiện quá trình ngược lại quá trình cập nhật:
1. a) Trên cây BIT thứ x2 của S1, tìm các jy là các hậu duệ của y2, jy>y1
2. b) Trên cây BIT thứ jy của S2, tìm các ix là các hậu duệ của x 2, ix>x1
3. c) Giảm tổng tích luỹ trên (x2; y2) một lượng là tổng tích luỹ trên các ô hậu duệ (ix; jy).

Hàm tính tổng các ô trong hình chữ nhật có góc trái-dưới là ô (x1; y1) và góc phải-trên là ô
(x2; y2) giới thiệu trong ví dụ dưới đây:
3.      Ví dụ.  Giải bài tập MATSUM – Matrix Summation
Cho ma trận A (N × N) các số. BuggyD là nhà phân tích ma trận và anh ấy muốn biết tổng các
số trong một ma trận con bất kỳ của A, do đó muốn có một hệ thống cho kết quả ứng với các
truy vấn này. Hệ thống cũng đáp ứng ngay cả khi ma trận biến động chấp nhận các giá trị mới
trên các ô của ma trận. Giả sử ban đầu các ô của ma trận đều bằng 0. Bạn hãy xây dựng hệ
thống cho BuggyD. Đọc khuôn dạng input cho chi tiết dưới đây

Input
Dòng đầu tiên của input chứa số nguyên t, là số các test. Tiếp theo là t test. Dòng đầu của mỗi
test chứa số nguyên N (1 <= N <= 1024), là chiều rộng ma trận. Tiếp theo là danh sách các
lệnh, mỗi lệnh có một trong 3 dạng sau:
SET x y num – Đặt giá trị tại ô (x, y) (0 <= x, y < N) thành num
SUM x1 y1 x2 y2 – Tìm và xuất ra tổng các số trong hình chữ nhật từ ô (x1, y1) đến ô (x2, y2),
kể cả chúng, x1 <= x2 và y1 <= y2, và kết quả không vượt quá số nguyên 32 bit.

END – Kết thúc một test.

Output
Với mỗi test, output trên một dòng cho mỗi lệnh SUM. Để một dòng trống sau mỗi test.

Ví dụ

Input:

1
4
SET 0 0 1
SUM 0 0 3 3
SET 2 2 12
SUM 2 2 2 2
SUM 2 2 3 3
SUM 0 0 2 2
END
Output
1
12
12
13
Chương trình.
#include <iostream>
#include <fstream>
using namespace std;
#define LL long long
LL tree[1050][1050];
void update(int x,int y,int val,int MAX){
    while(x<=MAX)    {
        int ty = y;
        while(ty <= MAX)        {
            tree[x][ty] += val;
            ty += (ty & -ty);
        }
        x += (x & -x);
    }
}
LL read(int x,int y){  //Tổng các số trong hcn từ (0,0) đến (x-1, y-1)
    LL sum = 0;
    while( x )    {
        int ty = y;
        while( ty )        {
            sum += tree[x][ty];
            ty -= (ty & -ty);
        }
        x -= (x & -x);
    }
    return sum;
}
int main(){
    int t;
    freopen(“matsum.inp”,”r”,stdin);
    freopen(“matsum.out”,”w”,stdout);
    scanf(“%d”,&t);
    while(t–) {
        int n;
        scanf(“%d”,&n);
        memset(tree,0,sizeof tree);
        while(1) {
            char s[10];
            scanf(“%s”,s);
            if(s[1] == ‘E’){ //SET
                int x,y,val;
                scanf(” %d%d%d”,&x,&y,&val);//Yêu cầu đặt giá trị tại ô (x,y)thành val
                                       //Giá trị đã có tại ô (x,y)
                LL p_val=read(x+1,y+1)+read(x,y)-read(x+1,y)-read(x,y+1);
                update(x+1,y+1,val-p_val,n+9); // Thêm val-p_val vào ô (x,y)
            }
            else if(s[1] == ‘U’){ //SUM
                LL sum = 0;
                int x1,y1,x,y;
                scanf(” %d%d%d%d”,&x,&y,&x1,&y1); //Xuất ra tổngtrong hcn(x,y,x1,y1)
                sum=read(x1+1,y1+1)+read(x,y)-read(x,y1+1)-read(x1+1,y);
                printf(“%lld\n”,sum);
            }
            else{
                //printf(“\n”);
                break;
            }
        }
        printf(“\n”);
    }
    return 0;
}
Ghi chú.

read(x1+1,y1+1): cho tổng các số trong hcn OEBF


read(x,y): cho tổng các số trong hcn OHDK
read(x,y1+1): cho tổng các số trong hcn OEAK
read(x1+1,y); cho tổng các số trong hcn OHCF
Do đó: read(x1+1,y1+1)+read(x,y)-read(x,y1+1)-read(x1+1,y);

cho tổng các số trong hcn ABCD


4. Bài toán ứng dụng.
Giả thiết một thế hệ thứ 4 điện thoại di động (mobile phone) có các trạm làm việc nằm trong
vùng Tampere hoạt động như sau: Vùng hoạt động này được chia theo lưới ô vuông. Các ô
vuông tạo thành một ma trận SxS với các hàng và cột được đánh số từ 0 đến S-1. Mỗi ô vuông
chứa một trạm làm việc. Số lượng các điện thoại đang hoạt động trong một ô vuông sẽ bị thay
đổi khi người sử dụng điện thoại di chuyển từ ô này sang ô khác hoặc điện thoại chuyển chế
độ bật/tắt. Theo thời gian, mỗi trạm làm việc sẽ báo cáo sự thay đổi số lượng điện thoại di
động đang hoạt động trong khu vực kiểm soát của mình.

Hãy viết chương trình nhận các báo cáo đó và trả lời được các yêu cầu về tổng số điện thoại di
động đang hoạt động trong một vùng không gian hình vuông cho trước. Hãy viết chương
trình  tiếp nhận các báo cáo này và trả lời các yêu câu cho biết tổng số điện thoại đang hoạt
động trong một hình chữ nhật của vùng.
Input: Dữ liệu vào đọc từ tệp input chuẩn như các số nguyên và trả lời các chất vấn ghi vào
output chuẩn như các số nguyên. Dữ liệu vào đã được mã hoá như sau: Mỗi dữ liệu vào trên
một dòng riêng gồm một số nguyên chỉ dẫn và một số tham số nguyên như bảng sau:
Số nguyên chỉ Các số nguyên Ý nghĩa
dẫn tham số

0 S Khởi trị ma trận kích thước SS. Chỉ dẫn này chỉ cho một lần
và là chỉ dẫn đầu tiên

1 XYA Thêm A điện thoại hoạt động trong trạm (x,y)

2 LBRT Hỏi tổng số điện thoại đang hoạt động trong các trạm (x,y)
với L≤x≤R, B≤y≤T.

3 Dừng chương trình. Chỉ dẫn này chỉ cho một lần và là chỉ
dẫn cuối cùng.

Các giá trị luôn trong phạm vi, không cần kiểm tra. Riêng A có thể âm, khi đó không giảm giá
trị trạm xuồng dưới 0. Các chỉ số bắt đầu từ 0 ví dụ bảng 44 thì có 0 x3, 0y3. Chương trình
của bạn sẽ không trả lời cho bất kì chỉ dẫn nào khác 2. Nếu chỉ dẫn là 2 thì chương trình của
bạn cần trả lời chất vấn bằng ghi trả lời trên đúng một dòng chứa số nguyên trên tệp output.

Stdin(input Stdout(output Giải thích


chuẩn) chuẩn)

04 Khởi trị bảng 4´4

1123 Cập nhật bảng tại ô (1,2) với +3

20022 Chất vấn tổng trong hình chữ nhật gồm các ô (x,y) mà
0≤x≤2; 0≤y≤2

3 Trả lời chất vấn

1112 Cập nhật bảng tại ô (1,1) với +2

1 1 2 -1 Cập nhật bảng tại ô (1,2) với -1


21123 Chất vấn tổng trong hình chữ nhật gồm các ô (x,y) mà
1≤x≤2; 1≤y≤3

4 Trả lời chất vấn

3 Dừng chương trình

Hạn chế:

Kích thước bảng S´S 1´1 ≤ S´S ≤ 1024´1024

Giá trị mỗi ô là V tại bất kì thời điểm nào V 0 ≤ V  ≤ 215-1 (=32767)

Lượng cập nhật A -215  ≤ A  ≤ 215-1

Số lượng chỉ dẫn trong input U 3  ≤ U  ≤ 60002

Số tối đa điện thoại trong bảng M M=230

Trong 20 test input có 16 test kích thước bảng tối đa là 512.

Hướng dẫn. Xây dựng cây BIT_2D. Lưu ý, trong các cây BIT thuộc tập S1 và tập S2, các nút
được đánh số từ 1 đến N, nhưng trong bài toán này dòng và cột của mảng đánh số từ 0.

You might also like