2021. 10. 16. 04:56ㆍ알고리즘/백준
1. 문제
https://www.acmicpc.net/problem/5419
5419번: 북서풍
각 테스트 케이스에 대해서, 북서풍을 타고 항해할 수 있는 섬의 쌍의 수를 출력한다.
www.acmicpc.net
2. 풀이
- Y 는 감소해야하고 X 는 증가해야한다.
- 이 때 생각할 수 있는 가장 간단한 방법은
- 한 축을 기준으로 정렬을 하고 한개씩 앞에서부터 다른 한축과 비교를 통해 가능한 쌍의 개수를 새나가는것이다.
- 이경우 O(N^2) 의 시간복잡도로 75000 * 75000 = 5,625,000,000
- 최악의 경우 TLE 가 뜰것이다.
그러므로 O(NlogN) 로 문제를 해결해야한다.
- 일단 x축을 기준으로 오름차순으로 정렬, y축은 내림차순으로 정렬해보자.

- 그러면 대충 위와 같은 그림과 같이 나올 것이다.
- 그럼 이제 한 x 에서 쌍을 이룰 수 있는 범위를 그려보자.

- 위 그림과 같이 Y 가 자신보다 작고 자신보다 앞에 있는것들과 쌍을 이룰 수 있다.
- 즉, 구간안에서 Y의 값이 해당 y값보다 작은 것들을 계산하는 구간 트리를 구현하면 문제가 해결된다는 것.
- 일단 이 구간트리를 구현하기 앞서
- 1e9 의 입력이 들어오는 y축의 좌표를 압축해야한다 (구간 트리의 끝노드의 범위를 줄여 메모리 절약)
- 압축은 y축을 map에 담아 정렬하여 작은것부터 0 1 2, ... n순서대로 좌표를 할당해주면된다. (아래 코드는 반대 방향)
for (auto it = dic.end(); it != dic.begin();)
{
it--;
it->second = maxN++; // 가장 큰값이 0번
}
for (auto &p : point)
{
p.second = dic[p.second];
}

- 이제 크기순으로 재할당된 번호 0, 1, 2, 3 ... 을 끝 노드로 가지는 빈 세그먼트 트리를 만들고,
- 이 세그먼트 트리에 x가 감소하는 방향으로 탐색하면서
- i ~ n까지의 구간에서 y값이 지금 탐색하고 있는 y보다 더 작은 개수를 먼저 쿼리하고,
- i 번째에서 y번호를 세그먼트 트리에 추가하여 추가된 노드들의 개수를 가지도록 구간 트리를 update 하면
(y를 크기순으로 정렬하고 0에서부터 번호를 부여했으므로 이때 까지 추가된 노드들 중에서 y값보다 더 작은 값의 개수를 구할 수 있다.)
- 문제는 해결된다.
3. 코드
- 구현은 풀이와 반대 방향으로 작성하였다.

// y 는 감소 해야하고 x는 증가해야한다.
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
#include <queue>
#include <stack>
#include <map>
using namespace std;
using pii = pair<int, int>;
using ll = long long;
vector<pii> point; // x, y
int n;
int maxN = 0;
struct RQ
{
vector<int> seg;
RQ(int n)
{
seg.clear();
seg.resize(n * 4 + 4, 0);
}
int query(int lo, int hi, int idx, int qlo, int qhi)
{
if (hi < qlo || qhi < lo)
{
return 0;
}
if (qlo <= lo && hi <= qhi)
{
return seg[idx];
}
int mid = (lo + hi) / 2;
int left = query(lo, mid, idx * 2, qlo, qhi);
int right = query(mid + 1, hi, idx * 2 + 1, qlo, qhi);
return left + right;
}
int update(int lo, int hi, int idx, int pos)
{
if (hi < pos || lo > pos)
{
return seg[idx];
}
if (lo == hi)
{
return seg[idx] = seg[idx] + 1;
}
int mid = (lo + hi) / 2;
int left = update(lo, mid, idx * 2, pos);
int right = update(mid + 1, hi, idx * 2 + 1, pos);
return seg[idx] = left + right;
}
};
ll solve()
{
ll answer = 0;
RQ rq(maxN);
for (int i = 0; i < point.size(); i++)
{
answer += (ll)rq.query(0, maxN, 1, 0, point[i].second);
rq.update(0, maxN, 1, point[i].second);
}
return answer;
}
int main()
{
cin.tie(0)->sync_with_stdio(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
{
maxN = 0;
cin >> n;
point.clear();
point.resize(n);
map<int, int> dic;
for (int i = 0; i < n; i++)
{ // x , y
cin >> point[i].first >> point[i].second;
dic[point[i].second]++;
}
sort(point.begin(), point.end(), [](pii &a, pii &b)
{
if (a.first == b.first)
{
return a.second > b.second;
}
else
{
return a.first < b.first;
}
});
for (auto it = dic.end(); it != dic.begin();)
{
it--;
it->second = maxN++;
}
for (auto &p : point)
{
p.second = dic[p.second];
}
cout << solve() << "\n";
}
}
4. 비슷한 문제
- 어떠한 한 값을 기준으로 그 값보다 작은 것의 개수 와 큰 것의 개수를 구하는것
- 이때 17131번은 스택같은 자료구조를 이용하여 y값이 같은것은 한번에 업데이트 시켜야한다.
'알고리즘 > 백준' 카테고리의 다른 글
[백준][C++] 23570번 Grid Triangle (math) (0) | 2021.11.16 |
---|---|
[백준][C++] 20036번 Ball Alignment (dp) (0) | 2021.10.07 |
[백준] [C++] 1533번 길의 개수 (그래프 변환, 행렬 제곱) (0) | 2021.09.18 |
[백준][C++] 4206번 피보나치 단어 (dp, kmp) (0) | 2021.08.30 |
[백준] [C++] 11400번 단절선 (dfs) (0) | 2021.07.22 |