본문 바로가기

알고리즘

[BOJ] 10167번: 금광

 

 

10167번: 금광

첫 줄에는 금광들의 개수 N (1 ≤ N ≤ 3,000)이 주어진다. 이어지는 N개의 줄 각각에는 금광의 좌표 (x,y)를 나타내는 음이 아닌 두 정수 x와 y(0 ≤ x,y ≤ 109), 그리고 금광을 개발하면 얻게 되는 이익

www.acmicpc.net

사용 알고리즘

  • plane sweeping
  • segment tree

시간 복잡도

  • O($N^2 log N$)

풀이

N이 최대 3000이기 때문에 시간 복잡도가 최대 O($N^2 logN$)인 알고리즘을 사용하여야 한다.

 

금광들을 직사각형 모양의 영역으로 묶어 이익을 확인할 경우,

금광들의 가장 좌측 상단에 있는 금광과 가장 우측 하단에 있는 금광을 기준으로 직사각형을 만들 수 있다.

따라서 금광들의 위치만으로 분할정복을 통해 구간의 합을 구하는 문제로 접근할 수 있다. 

x축을 sweeping한 후 y축을 기준으로 세그먼트 트리를 구현할 때 각 구간에서는 합은 다음과 같이 정의한다.

 

  • lsum  -> 구간의 왼쪽 끝에서부터의 연속한 원소의 최대 합
  • rsum  -> 구간의 오른쪽 끝에서부터의 연속한 원소의 최대 합
  • tsum  -> 구간의 모든 원소의 합
  • msum -> 구간에서 나올 수 있는 최대 연속합

구간의 합은 세그먼트 트리를 이용하여 업데이트와 쿼리를 O($logN$)에 구할 수 있다.

따라서 구간의 왼쪽 끝의 x축을 기준으로 잡고 오른쪽 끝의 x축을 늘려가며 업데이트와 쿼리를 통해 확인해 주면 된다.

이때, x축을 늘려가며 업데이트를 진행할 때 같은 x 좌표의 y축 값들은 동시에 업데이트를 진행해주어야 한다.

 

x축을 늘릴 때마다 각 점당 O($logN$)씩 걸리게 되고

왼쪽 끝의 x축을 기준으로 오른쪽의 모든 점들을 업데이트와 쿼리를 진행했을 때 O($NlogN$)이 걸린다.

따라서 모든 x축에 대해서 왼쪽 끝의 x축을 변경할 때 세그먼트 트리를 초기화 후 진행하면 O($N^2 log N$)이 걸린다.

 

x축 기준으로 좌표 압축하여 동일 x축 상의 y 좌표를 한 번에 업데이트할 수 있도록 진행하고,

y축 기준으로 좌표 압축하여 세그먼트 트리의 리프 노드를 최대 N만큼만 사용할 수 있게 만들어 준다.

 

x축 압축은 편의상 진행을 해준 경우지만,

y축 압축은 하지 않으면 리프 노드 수를 좌표 범위($10^9$) 만큼 잡아야 하기에 좌표 압축을 반드시 해주어야 한다.


소스코드

#include <bits/stdc++.h>
using namespace std;

class Node {
public:
	long long tsum, lsum, rsum, msum;
	
	Node() : tsum{ 0 }, lsum{ 0 }, rsum{ 0 }, msum{ 0 } {};
	Node sum(int val) {
		this->tsum += val;
		this->lsum = this->rsum = this->msum = this->tsum;
		return *this;
	}
	Node merge(const Node& left, const Node& right) {
		this->tsum = left.tsum + right.tsum;
		this->lsum = max(left.lsum, left.tsum + right.lsum);
		this->rsum = max(right.rsum, left.rsum + right.tsum);
		this->msum = max({ left.rsum + right.lsum, left.msum, right.msum, this->lsum, this->rsum });
		return *this;
	}
};

class Dot {
public:
	int x, y, w;

	Dot() {};
	Dot(int x, int y, int w) : x{ x }, y{ y }, w{ w } {};
};

long long res;
int N, xs_len, ys_len;

vector < Node > tree;
vector < int > xs, ys;
vector < Dot > dot_list;
vector < vector<Dot> > zip;

Node update(int inx, int val, int node, int x, int y) {
	if (y < inx || inx < x)
		return tree[node];
	if (x == y)
		return tree[node].sum(val);

	int mid = (x + y) >> 1;
	return tree[node].merge(update(inx, val, node * 2, x, mid), update(inx, val, node * 2 + 1, mid + 1, y));
}

int main() {
	ios_base::sync_with_stdio(false); cin.tie(NULL);

	cin >> N;
	dot_list.resize(N);
	for (Dot& dot : dot_list) {
		cin >> dot.x >> dot.y >> dot.w;
		xs.push_back(dot.x);
		ys.push_back(dot.y);
	}
	sort(xs.begin(), xs.end());
	sort(ys.begin(), ys.end());

	xs.erase(unique(xs.begin(), xs.end()), xs.end());
	xs_len = xs.size();
	ys.erase(unique(ys.begin(), ys.end()), ys.end());
	ys_len = ys.size();

	zip.resize(xs_len);
	for (Dot& dot : dot_list) {
		int x = lower_bound(xs.begin(), xs.end(), dot.x) - xs.begin();
		int y = lower_bound(ys.begin(), ys.end(), dot.y) - ys.begin();

		zip[x].push_back(Dot(x, y, dot.w));
	}

	for (int i = 0; i < xs_len; i++) {
		tree.resize(ys_len * 4);

		for (int j = i; j < xs_len; j++) {
			for (Dot& k : zip[j])
				update(k.y, k.w, 1, 0, ys_len - 1);
			res = max(tree[1].msum, res);
		}

		tree.clear();
	}

	cout << res;

	return 0;
}

 

Support by 강태종

'알고리즘' 카테고리의 다른 글

[BOJ] 11444번: 피보나치 수 6  (0) 2020.11.03