คู่มือโอลิมปิกคอมพิวเตอร์

Computer Olympiad Guide for Thai High School Students

Array Manipulation

คำอธิบาย

โจทย์กลุ่ม array manipulation คือโจทย์ที่มีข้อมูลเรียงเป็นลำดับ แล้วต้องตอบคำถามหรือแก้ไขค่าในช่วงของ array ให้เร็วกว่าไล่ดูทุกช่องทุกครั้ง

ตัวอย่างคำถามที่พบบ่อย

  • หาผลรวมช่วง [l, r]

  • เพิ่มค่าทุกตำแหน่งในช่วง [l, r]

  • หาค่าน้อยสุดหรือมากสุดในช่วง

  • นับจำนวนค่าที่ตรงเงื่อนไขบางอย่าง

Motivation problem

ถ้ามี array ขนาด n และมีคำถาม q ครั้ง แต่ละครั้งถามผลรวมตั้งแต่ตำแหน่ง l ถึง r วิธีตรงไปตรงมาคือวนตั้งแต่ l ถึง r

long long sum = 0;
for (int i = l; i <= r; i++) {
  sum += a[i];
}

ถ้าแต่ละ query ใช้เวลา O(n) เวลารวมจะเป็น O(nq) ซึ่งช้าเกินไปเมื่อ n, q มีค่าประมาณ 105

Prefix sum

ถ้า array ไม่มีการแก้ไขค่า เราสามารถ preprocess ด้วย prefix sum ได้

pref[0] = 0;
for (int i = 1; i <= n; i++) {
  pref[i] = pref[i - 1] + a[i];
}

ผลรวมช่วง [l, r] คือ

pref[r] - pref[l-1]

เวลา preprocess คือ O(n) และตอบแต่ละ query ได้ใน O(1)

Difference array

ถ้าโจทย์ให้เพิ่มค่า x ทุกตำแหน่งในช่วง [l, r] หลายครั้ง แล้วค่อยถามค่า array สุดท้าย ใช้ difference array ได้

diff[l] += x;
diff[r + 1] -= x;

หลังจากทำทุก update แล้ว คำนวณค่าจริงด้วย prefix sum ของ diff

long long cur = 0;
for (int i = 1; i <= n; i++) {
  cur += diff[i];
  a[i] += cur;
}

เทคนิคนี้เหมาะกับโจทย์ offline ที่รู้ update ทั้งหมดก่อนตอบคำถาม

Fenwick Tree

Fenwick Tree หรือ Binary Indexed Tree ใช้ตอบ range sum และ point update ได้เร็ว

  • update หนึ่งตำแหน่ง: O(log n)

  • query prefix sum: O(log n)

  • ใช้หน่วยความจำ O(n)

vector<long long> bit(n + 1);

void add(int idx, long long val) {
  for (; idx <= n; idx += idx & -idx) {
    bit[idx] += val;
  }
}

long long sum(int idx) {
  long long res = 0;
  for (; idx > 0; idx -= idx & -idx) {
    res += bit[idx];
  }
  return res;
}

long long range_sum(int l, int r) {
  return sum(r) - sum(l - 1);
}

ใช้กับโจทย์ Dynamic Range Sum Queries 1 ได้โดยตรง

Segment Tree

Segment Tree เหมาะเมื่อ operation ซับซ้อนกว่า sum เช่น min, max, gcd หรือมี range update

  • build: O(n)

  • query ช่วง: O(log n)

  • update หนึ่งตำแหน่ง: O(log n)

  • ใช้หน่วยความจำประมาณ O(4n)

vector<long long> seg(4 * n);

void build(int node, int l, int r) {
  if (l == r) {
    seg[node] = a[l];
    return;
  }
  int mid = (l + r) / 2;
  build(node * 2, l, mid);
  build(node * 2 + 1, mid + 1, r);
  seg[node] = seg[node * 2] + seg[node * 2 + 1];
}

เลือกใช้อะไรดี

เทคนิค

update

query

เหมาะกับ

Prefix sum

O(1) หลัง preprocess

O(1)

ไม่มีการแก้ค่า

Difference array

O(1) ต่อช่วง

O(n) เพื่อสรุปผล

update หลายช่วงแบบ offline

Fenwick Tree

O(log n)

O(log n)

sum และ frequency

Segment Tree

O(log n)

O(log n)

operation หลากหลาย

แหล่งฝึก

  • Fenwick Tree 2

  • Segment Tree 3

  • Visualgo Fenwick Tree 4

  • Visualgo Segment Tree 5


  1. https://cses.fi/problemset/task/1648/↩︎

  2. https://csacademy.com/lesson/fenwick_trees/↩︎

  3. https://csacademy.com/lesson/segment_trees/↩︎

  4. https://visualgo.net/en/fenwicktree?slide=1↩︎

  5. https://visualgo.net/en/segmenttree?slide=1↩︎