SEUNGHWAN KIM

바텀업 금광 세그와 교환법칙

TAGSALGORITHMDATA STRUCTURE

바텀업 세그먼트 트리는 탑다운 구현에 비해 코드가 짧고 속도가 빠르다. 하지만 복잡한 연산을 위한 확장이 불편하거나, 혹은 잘 알려지지 않아서 인기가 없는 편이다.

물론 금광 세그에 대한 한국어 자료도 본 적이 없다. 한국에서는 교환법칙이 성립하지 않는 세그먼트 트리를 BOJ 10167 금광에서 따와 금광 세그라고 부른다. 참고 세그먼트 트리는 모노이드에 관한 자료구조여서 교환법칙이 성립하지 않아도 된다. 참고

바텀업 금광 세그는 아주 간단하게 구현할 수 있다. 우선 기본 코드를 보자.

struct seg {
  struct node {
    ll lo, hi, sum, val;
  };

  int n;
  vector<node> tree;

  static node op(const node &a, const node &b) {
    return {max(a.lo, a.sum + b.lo), max(b.hi, b.sum + a.hi), a.sum + b.sum,
            max({a.hi + b.lo, a.val, b.val})};
  }

  void update(int x, ll v) {
    tree[x += n] = {tree[x].val + v, tree[x].val + v, tree[x].val + v,
                    tree[x].val + v};
    for (x /= 2; x > 0; x /= 2)
      tree[x] = op(tree[x * 2], tree[x * 2 + 1]);
  }
};

사실 금광 문제는 업데이트 함수만 필요해서 여기까지만 짜도 괜찮다. 바텀업 세그와 금광 세그를 짜본 사람이라면 어렵지 않은 코드일 거다.

BOJ 16997 히스토그램에서 가장 큰 직사각형과 쿼리는 범위에 대한 쿼리가 있어서 쿼리 함수를 구현해야 한다.

// wrong implementation
node query(int l, int r) {
    node ret{ 0, 0, 0, 0 };
    for (l+=n, r+=n; l<=r; l/=2, r/=2) {
        if (l%2)
            ret = op(tree[l++], ret);
        if (r%2 == 0)
            ret = op(tree[r--], ret);
    }
    return ret;
}

이건 잘못된 구현으로 교환법칙이 성립해야만 작동한다.

여기서 ret 에 값을 누적하는 과정을 잘 보자. 왼쪽과 오른쪽에서 각각, 바깥쪽부터 안쪽으로 값을 누적하고 있다. 이 부분만 잘 고치면 된다.

왼쪽과 오른쪽 누적값을 분리하고, 누적 자체도 좌우 순서를 지키자.

// right implementation
node query(int l, int r) {
    node retl{ 0, 0, 0, 0 }, retr = retl;
    for (l+=n, r+=n; l<=r; l/=2, r/=2) {
        if (l%2)
            retl = op(retl, tree[l++]);
        if (r%2 == 0)
            retr = op(tree[r--], retr);
    }
    return op(retl, retr);
}

잘 작동한다.

2024 © SEUNGHWAN KIMRSS