Fenwick Tree

좜처: https://cp-algorithms.com/data_structures/fenwick.html

Let, ff be some reversible function and A be an array of integers of length N.

νŽœμœ…νŠΈλ¦¬λŠ” λ‹€μŒκ³Ό 같은 자료ꡬ쑰λ₯Ό λ§ν•œλ‹€.

  • [l:r][l:r] 에 λŒ€ν•΄ ν•¨μˆ˜ ff λ₯Ό O(logn)O(logn)에 계산할 수 μžˆλ‹€.
  • AA 의 ν•œ μš”μ†Œλ₯Ό O(logn)O(logn)에 κ°±μ‹ ν•  수 μžˆλ‹€.
  • O(N)O(N) 의 λ©”λͺ¨λ¦¬λ₯Ό μš”κ΅¬ν•œλ‹€. ⭐
  • μ½”λ“œκ°€ 짧고 μ’‹λ‹€. (multidimensional array에 λŒ€ν•΄μ„œλ„)

νŽœμœ…νŠΈλ¦¬λŠ” Binary Indexed Tree 라고도 λΆ€λ₯Έλ‹€. 보톡 range sum을 ꡬ할 λ•Œ 많이 μ‚¬μš©ν•œλ‹€.

HOW

λ°°μ—΄ A[0...Nβˆ’1]A[0...N-1]κ°€ μžˆλ‹€κ³  ν•˜μž. νŽœμœ… νŠΈλ¦¬λŠ” λ°°μ—΄ T[0...Nβˆ’1]T[0...N-1]이고 각 μš”μ†ŒλŠ” λ°°μ—΄ AA 의 [g(i):i][g(i):i] 의 합에 λŒ€μ‘λœλ‹€.

​ Ti=βˆ‘j=g(i)iAjT_i = \sum^i_{j=g(i)}A_j

λ°°μ—΄ TT λŠ” λ°°μ—΄μ΄μ§€λ§Œ 맀우 πŸ‘ nice ν•˜κ²Œ 트리둜 ν‘œν˜„ν•  수 μžˆλ‹€. νŽœμœ…νŠΈλ¦¬μ—μ„œ ν•„μš”ν•œ 것은 TT 뿐이고 λͺ¨λ“  쿼리λ₯Ό μ²˜λ¦¬ν•  수 μžˆλ‹€.

// [0:r]κΉŒμ§€ sum 
def sum(int r):
	result = 0
	while (r>=0)
		res += t[r]
		r = g(r) - 1
	return res
	
// update A[i] = A[i] + delta
def increase(int i, int delta):
	for all j with g(j) <= i <= j:
		t[j] += delta

ν•¨μˆ˜ sum 은 λ‹€μŒκ³Ό 같이 λ™μž‘ν•œλ‹€:

  1. result 에 [g(r);r][g(r);r] κΉŒμ§€μ˜ 합을 λ”ν•œλ‹€.
  2. 그리고 λ‹€μŒ rangeλ₯Ό λ”ν•œλ‹€;[g(g(r)βˆ’1):g(r)βˆ’1][g(g(r)-1):g(r)-1]
  3. κ³„μ†ν•΄μ„œ [0;g(g(...(g(r)βˆ’1...βˆ’1)=1)][0;g(g(...(g(r)-1...-1)=1)] μ—μ„œ [g(βˆ’1);1][g(-1);1] κΉŒμ§€ λ”ν•œλ‹€.

ν•¨μˆ˜ increase λŠ” λ‹€μŒκ³Ό 같이 λ™μž‘ν•œλ‹€. = update

  1. λ‹€μŒμ„ λ§Œμ‘±ν•˜λŠ” [g(j);j][g(j);j] κΉŒμ§€μ˜ 합을 κ΅¬ν•œλ‹€.

    g(j)≀i≀jg(j) \le i \le j λ₯Ό λ§Œμ‘±ν•˜λŠ” t[j]t[j] λ₯Ό delta μ”© μ¦κ°€μ‹œν‚¨λ‹€. 즉, t[j] += delta .

sum κ³Ό increase 의 μ‹œκ°„λ³΅μž‘λ„λŠ” ν•¨μˆ˜ g 에 영ν–₯을 λ°›λŠ”λ‹€. λͺ¨λ“  ii 에 λŒ€ν•˜μ—¬ 0≀g(i)≀i0 \le g(i) \le i λ₯Ό λ§Œμ‘±ν•œλ‹€λ©΄ ν•¨μˆ˜ gg λ₯Ό μ„ νƒν•˜λŠ” 방법은 λ§Žλ‹€. g(i)=ig(i)=i 라고 μƒκ°ν•΄λ³΄μž. 그러면 T=AT = A κ°€ 될 뿐이닀. κ·ΈλŸ¬λ―€λ‘œ g(i)=ig(i)=i λ₯Ό μ„ νƒν•œλ‹€λ©΄ ꡬ간합을 κ΅¬ν•˜λŠ” μ‹œκ°„μ€ μ—¬μ „νžˆ 느릴 것이닀.

g(i)=0g(i) = 0 을 ν•¨μˆ˜λ‘œ ν•˜λ©΄ μ–΄λ–¨κΉŒ? μ΄λŠ” [0;i][0;i] κΉŒμ§€ 합을 κ΅¬ν•œλ‹€λŠ” μ˜λ―Έμ΄λ―€λ‘œ 읡히 μ•Œλ‹€μ‹œν”Ό ꡬ간합을 O(1)O(1) 에 μ²˜λ¦¬ν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ 갱신은 λŠλ¦¬λ‹€.

νŽœμœ… μ•Œκ³ λ¦¬μ¦˜μ˜ 쒋은 점이 μ—¬κΈ° μžˆλ‹€. ν•¨μˆ˜ gg λ₯Ό νŠΉλ³„ν•˜κ²Œ μ •μ˜ν•¨μœΌλ‘œμ¨ ꡬ간쿼리와 갱신을 O(logN)O(logN) 에 μ²˜λ¦¬ν• μˆ˜ μžˆλ‹€.

g(i)g(i) 의 μ •μ˜

g(i)g(i) λŠ” λ‹€μŒκ³Ό 같이 μ •μ˜ν•  수 μžˆλ‹€.

iλ₯Ό 2μ§„μˆ˜λ‘œ ν‘œν˜„ν–ˆμ„ λ•Œ, κ°€μž₯ λ§ˆμ§€λ§‰μ— λ‚˜μ˜€λŠ” 0의 뒀에 μžˆλŠ” 1을 λͺ¨λ‘ 0으둜 λ°”κΎΌλ‹€.

g(11)=g(10112)=10002=8g(11) = g(1011_2) = 1000_2=8

g(12)=g(11002)=11002=12g(12)=g(1100_2)=1100_2=12

g(13)=g(11012)=11002=12g(13)=g(1101_2)=1100_2=12

g(14)=g(11102)=11102=14g(14)=g(1110_2)=1110_2=14

g(15)=g(11112)=11112=0g(15)=g(1111_2)=1111_2=0

μ΄λŸ¬ν•œ ν•¨μˆ˜ ggλŠ” λ‹€μŒκ³Ό 같이 μ •μ˜ν•  수 μžˆλ‹€.

​ ⭐ g(i)=iΒ &Β (i+1)g(i)=i \ \&\ (i+1) ⭐

이제 μ–΄λ–»κ²Œ ν•˜λ©΄ g(j)≀i≀jg(j) \le i \le j 인 jj λ₯Ό μ–΄λ–»κ²Œ μ•Œκ³  μˆœνšŒν• κΉŒ?

생각해보면 쉽닀. μš°μ„  ii λ₯Ό 2μ§„μˆ˜λ‘œ ν‘œν˜„ν•œλ‹€. κ·Έλ¦¬κ³ λ‚˜μ„œ μ°¨λ‘€λŒ€λ‘œ 0인 λΉ„νŠΈλ₯Ό 1둜 λ°”κΎΌλ‹€. μ΄λ ‡κ²Œ 되면 0인 λΉ„νŠΈκ°€ 1이 λ˜μ—ˆκΈ° λ•Œλ¬Έμ— i≀ji \le j λŠ” λͺ…λ°±ν•˜λ‹€. λ˜ν•œ g(j)g(j) λŠ” κ°€μž₯ λ§ˆμ§€λ§‰μœΌλ‘œ λ‚˜μ˜€λŠ” 0 λ’€μ˜ λΉ„νŠΈλ₯Ό λͺ¨λ‘ 0을 λ°”κΎΈκΈ° λ•Œλ¬Έμ— g(j)≀ig(j) \le i 이닀.

πŸ˜„ Case: i=10i = 10

10=00010102h(10)=11=00010112h(11)=15=00011112h(15)=31=00111112h(31)=63=01111112\hspace{1.9cm}10 = 0001010_2 \\ h(10) = 11 =0001011_2 \\ h(11) = 15 =0001111_2 \\ h(15)=31=0011111_2 \\ h(31) = 63 = 0111111_2

그리고 μ΄λŸ¬ν•œ hh λŠ” λΉ„νŠΈμ—°μ‚°μœΌλ‘œ κ°„λ‹¨ν•˜κ²Œ ꡬ할 수 μžˆλ‹€.

​ ⭐ h(j)=j∣(j+1)h(j) = j | (j+1) ⭐

μ΄λŸ¬ν•œ 연산을 톡해 TT λŠ” 트리의 ν˜•νƒœλ₯Ό λ„κ²Œ λœλ‹€.

Binary Indexed Tree

Finding sum in one-dimesional array

struct FenwickTree {
  vector<int> bit;
  int n;
  
  FenwickTree(int n) {
    this->n = n; 
    bit.assign(n, 0);
  }
  
  FenwickTree(vector<int> a):FenwickTree(a.size()) {
    for(size_t i=0; i<a.size(); ++i) {
      add(i, a[i]);
    }
  }
  
  int sum(int r) {
    int ret = 0;
    for(; r>=0; r = (r & (r+1)) - 1)
      ret+=bit[r];
    return ret; 
  }
  
  int sum(int l, int r) {
    return sum(r) - sum(l-1);
  }
  
  void add(int idx, int delta) {
    for(; idx<n; idx = idx | (idx+1))
      bit[idex] += delta; 
  }
};

Finding minimum of [0;r] in one-dimensional array

νŽœμœ…νŠΈλ¦¬λ₯Ό μ΄μš©ν•΄μ„œ [l,r][l,r] μ—μ„œ μ΅œμ†Ÿκ°’μ„ μ°ΎλŠ” μ‰¬μš΄ 방법은 μ—†λ‹€. νŽœμœ…νŠΈλ¦¬λŠ” [0;r][0;r] 에 λŒ€ν•΄μ„œλ§Œ μ•Œ 수 μžˆλ‹€.

struct FenwickTreeMin {
  vector<int> bit; 
  int n; 
  const int INF = (int)1e9; 
  
  FenwickTreeMin(int n) {
    this->n = n;
    bit.assing(n, INF); 
  }
  
  FenwickTree(vector<int> a) : FenwickTreeMin(a.size()) {
    for (size_t i = 0; i < a.size(); ++i) {
      update(i, a[i]);
    }
  }
  
  int getMin(int r) {
    int ret = INF;
    for(; r >= 0; r = (r & (r+1)) - 1) {
      ret = min(ret, bit[r]);
    }
    return ret; 
  }
  
  void update(int idx, int val) {
    for(; idx<n; idx = idx | (idx + 1)) {
      bit[idx] = min(bit[idx], val);
    }
  }
}

Finding sum in two-dimensional array

struct FenwickTree2D {
  vector<vector<int>> bit; 
  int n, m; 
  
  FenwickTree2D(int n, m) {
    this->n = n; 
    this->m = m; 
    bit.assign(n, vector<int>(m, 0));
  }
  
  int sum(int x, int y) {
    int ret = 0;
    for(int i=x; i>=0; i = (i & (i+1))-1) {
      for(int j=y; j>=0; j = (j & (j+1))-1) 
        ret += bit[i][j];
    }
    return ret;
  }
  
  void add(int x, int y, int delta) {
    for(int i=x; i<n; i = i | (i+1)) {
      for(int j=y; j<m; j = j | (j+1)) 
        bit[i][j] += delta; 
    }
  }
}

One-based indexing approch

T[]T[] λž‘ g()g() λ₯Ό 쑰금 λ‹€λ₯΄κ²Œ μ •μ˜ν•΄λ³΄μž. T[i]T[i] λŠ” [g(i)+1;i][g(i)+1;i] 의 합을 μ €μž₯ν•œλ‹€. μ™œ μ΄λ ‡κ²Œ ν• κΉŒ? κ·Έ μ΄μœ λŠ” TT λ₯Ό μ΄λ ‡κ²Œ μ •μ˜ν•˜λ©΄ g(i)g(i) λ₯Ό πŸ‘ ν•˜κ²Œ μ •μ˜ν•  수 μžˆλ‹€.

def sum(int r):
	res = 0
	while(r>0):
		res+=t[r]
		r=g(r)
	return res;

def increase(int i, int delta):
	for all j with g(j) < i <= j:
		t[j] += delta

이제 g(i)g(i) λ₯Ό λ‹€μŒκ³Ό 같이 μ •μ˜ν•  수 μžˆλ‹€.

iλ₯Ό 2μ§„μˆ˜λ‘œ ν‘œν˜„ν–ˆμ„ λ•Œ κ°€μž₯ λ§ˆμ§€λ§‰μ˜ 1을 0으둜 λ°”κΎΌλ‹€.

g(7)=g(1112)=1102=6g(7) = g(111_2) = 110_2 = 6

g(6)=g(1102)=1002=4g(6) = g(110_2) = 100_2 =4

g(5)=g(1002)=0002=0g(5)=g(100_2)=000_2=0

κ°€μž₯ λ§ˆμ§€λ§‰ λΉ„νŠΈλŠ” i&(βˆ’i)i\&(-i) 둜 ν‘œν˜„ν•  수 있기 λ•Œλ¬Έμ— g(i)g(i) λŠ” λ‹€μŒκ³Ό 같이 ν‘œν˜„ν•  수 μžˆλ‹€.

​ ⭐g(i)=iβˆ’(i&(βˆ’i))g(i) = i-(i\&(-i))⭐

λ˜ν•œ h(i)h(i) 도 λ‹€μŒκ³Ό 같이 ν‘œν˜„ν•  수 μžˆλ‹€.

​ ⭐ h(i)=i+(i&(βˆ’i))h(i) = i + (i \&(-i)) ⭐

struct FenwickTreeOneBasedIndexing {
  vector<int> bit;
  int n; 
  
  FenwickTreeOneBasedIndexing(int n) {
    this->n = n + 1;
    bit.assing(n + 1, 0);
  }
  
  FenwickTreeOneBasedIndexing(vector<int> a) : FenwickTreeOneBasedIndexing(a.size()) {
    init(a.size());
    for(size_t i = 0; i < a.size(); ++i) {
      add(i, a[i]);
    }
  }
  
  int sum(int idx) {
    int ret = 0;
    for(++idx; idx>0; idx -= idx & -idx) 
      ret += bit[idx];
    return ret;
  }
  
  int sum(int l, int r) {
    return sum(r) - sum(l-1);
  }
  
  void add(int idx, int delta) {
    for(++idx; idx<n; idx += idx & -idx)
      bit[idx] += delta; 
  }
};

πŸ˜• μ–΄λ–»κ²Œ i&(-i) κ°€ λ§ˆμ§€λ§‰ λΉ„νŠΈκ°€ λ˜λŠ”μ§€μ— λŒ€ν•˜μ—¬

λΉ„νŠΈμ—μ„œ 음수λ₯Ό ν‘œν˜„ν•˜λŠ” 방법은 2의 보수 이닀. 2의 λ³΄μˆ˜λŠ” μ–΄λ–»κ²Œ κ΅¬ν• κΉŒμš”.

λ³΄μˆ˜λž€ 두 수의 합이 μ§„λ²•μ˜ λ°‘μˆ˜κ°€ 되게 ν•˜λŠ” 수 이닀. μ»΄ν“¨ν„°λŠ” λΊ„μ…ˆμ˜ κ°œλ…μ΄ μ—†κΈ° λ•Œλ¬Έμ— λ§μ…ˆμ„ 톡해 음수λ₯Ό κ΅¬ν•œλ‹€.

Range operations

1. Point update and range query

μ§€κΈˆκΉŒμ§€ ν–ˆλ˜ 것!

2. Range Update and Point query

3. Range Update and Range query


[jungin]
Written by@[jungin]
μ•ˆλ…•ν•˜μ„Έμš”

GitHub