티스토리 뷰

JOISC

JOISC14 Scarecrows

arnold518 2019. 3. 14. 20:54

문제

https://oj.uz/problem/view/JOI14_scarecrows

 

문제 보기 - 허수아비 (JOI14_scarecrows) :: oj.uz

JOI 마을에 있는 넓은 황무지에는 $N$명의 신성한 허수아비가 서 있어, 주민들은 일 년에 몇 번씩 허수아비를 둘러싸고 축제를 하고 있었습니다. JOI 마을의 촌장은 "허수아비들의 말씀을 들었다"고 주장하며 황무지에 밭을 하나 만들 계획을 세웠습니다. 허수아비들의 말에 따르면 밭은 다음 조건을 충족해야 한다고 합니다. 각 변이 동서 방향 또는 남북 방향으로 놓인 직사각형이어야 합니다. 남서쪽의 꼭짓점과 북동쪽의 꼭짓점에는 허수아비가 서 있어야 합니다.

oj.uz

평면 상에 N개의 점들이 주어져 있을때 다음 조건을 만족하는 점들의 순서쌍의 수를 세는 문제이다.

1. 이 두 점을 잇는 직선은 기울기가 양수이다.

2. 이 두 점을 꼭짓점으로 하는 직사각형의 내부에는 어떠한 점도 없다.

 

풀이

점들을 하나씩 오른쪽으로 훑고 지나가는 라인 스위핑을 하며, 그 점을 오른쪽 위 꼭짓점으로 하는 직사각형들의 수를 세 보자. 이 점을 "기준 점"이라 부르자.

 

 

이때, 왼쪽 아래 점이 될수 있는 점들은 기준 점에서부터 왼쪽으로 차례차례 봤을 때, y좌표가 기준 점보다 높은 점들을 제외하고, y좌표가 오름차순이여야 한다.

 

이때 이러한 점들의 수를 그냥 세주면 $O(N^2)$ 으로 비효율 적이다. 따라서 효율적인 자료구조를 사용해 시간을 $NlogN$ 으로 감소시켜야 한다.

 

점들을 노드에 저장하고 있는 세그먼트 트리를 만들자.

물론 점들 사이의 상대적인 위치만 중요하기 때문에 처음에 좌표압축을 해주고 시작한다.

이때 저장하고 있는 노드들은 y좌표를 기준으로 한다. ( y좌표가 1~10까지 존재한다면 1~5 사이의 점들을 노드에 저장하는 방식이다. )

 

노드에 저장해야 하는 값들은 현재까지 본 x좌표 구간 (1~xnow) 에서, 그 노드에게 할당된 y좌표 구간 (yl, yh) 구간의 점들 중 위 그림의 검정색 점처럼 앞에서 보면 내림차순 모양의 노드들만 x좌표 순으로 저장해 준다.

여기서 인지해야 할 점은 세그트리의 아래 단계에서 이미 제거당한 점들은 그 위의(부모의) 노드들에서 다시 부활할 일이 없다는 점이다.

 

이렇게 세그트리를 정의하면, 두가지 연산을 $logN$ 만에 처리해야 한다.

 

1. update

어떠한 내림차순 수열에 새로운 점 하나가 추가되었다.

이 새로운 점은 가장 최신에 본 점이기 때문에, x좌표가 가장 크다.

따라서 이미 저장된 배열에 이 점보다 y좌표가 작은 점들이 있다면 제거해 준 후 이 점을 추가하는 연산을 반복해 주면 된다.

히스토그램 문제나, Bad Hair Day 문제에서 스택을 이용해 라인 스위핑으로 푸는 것과 매우 비슷한 느낌으로, 각 점은 최대 $logN$ 개의 구간에 포함되며, 최대 한번 들어갔다, 나오기 때문에 ammortized time complexity 는 $NlogN$이다.

시간 복잡도 : $O(NlogN)$

 

2. query

새로운 점이 들어오면 그 점을 오른쪽 꼭짓점으로 하는 값들을 알고 싶다.

따라서 세그트리에 1~y 에 대한 쿼리를 때리면 된다.

위 그림에서 빨간색 점들이 위 노드, 파란색 점들이 아래 노드이면, 직선으로 연결된 점들의 수를 세주고 싶다.

즉, 아래 노드의 x좌표 전까지 빨간색 점들을 타고 가다가 파란색으로 옮겨타면 된다.

이때 이용할 수 있는 것들은 점들이 x좌표 오름차순이자 y좌표 내림차순으로 정렬되어 있다는 것이다.

이는 바로 이분탐색이 가능함을 의미한다.

점들의 수와 마지막 점의 x좌표를 전역으로 저장하거나 함수의 인자로 넘겨주면 쉽게 구현할 수 있다.

어디서 아래 노드로 넘어가야 할지를 이진탐색으로 해주면 $logN$ 개의 노드들에 대해 이진탐색 $logN$ -> $log^2N$ 에 처리 가능하다.

시간 복잡도 : $O(log^2N)$

 

구현상 주의해야 할 부분들은 답이 long long 일 수 있으며, query 연산에서 배열이 비어있을 경우도 생각해야 한다는 점이다.

 

개인적으로 문제는 간단하고 풀이는 아름다운 매우 좋은 문제다.

 

시간 복잡도 : $O(NlogN+Nlog^2N) = O(Nlog^2N)$

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

typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;

const int MAXN = 2e5;

struct Point
{
    int x, y;
    Point() {}
    Point(int x, int y) : x(x), y(y) {}
    bool operator < (const Point& p)
    {
        return x<p.x;
    }
};

int n;
ll ans;
Point arr[MAXN+10];
vector<int> xpos, ypos;
int xcoord[MAXN+10];

vector<vector<Point>> tree;

void update(int node, int tl, int tr, int x, int y)
{
    if(tl==tr) { tree[node]=vector<Point>(1, Point(x, y)); return; }
    int mid=tl+tr>>1;
    if(y<=mid) update(node*2, tl, mid, x, y);
    else update(node*2+1, mid+1, tr, x, y);

    while(!tree[node].empty() && tree[node].back().y<y) tree[node].pop_back();
    tree[node].push_back({x, y});
}

int last=-1, ret=0;
void query(int node, int tl, int tr, int l, int r)
{
    if(r<tl || tr<l) return;
    if(l<=tl && tr<=r)
    {
        if(tree[node].empty()) return;
        ret+=tree[node].end()-lower_bound(tree[node].begin(), tree[node].end(), Point(last, 0));
        last=max(last, tree[node].back().x);
        return;
    }
    int mid=tl+tr>>1;
    query(node*2+1, mid+1, tr, l, r);
    query(node*2, tl, mid, l, r);
}

int main()
{
    int i, j;
    scanf("%d", &n);
    for(i=0; i<n; i++)
    {
        scanf("%d%d", &arr[i].x, &arr[i].y);
        xpos.push_back(arr[i].x);
        ypos.push_back(arr[i].y);
    }
    sort(xpos.begin(), xpos.end());
    sort(ypos.begin(), ypos.end());

    for(i=0; i<n; i++)
    {
        arr[i].x=lower_bound(xpos.begin(), xpos.end(), arr[i].x)-xpos.begin()+1;
        arr[i].y=lower_bound(ypos.begin(), ypos.end(), arr[i].y)-ypos.begin()+1;
    }

    sort(arr, arr+n, [&](const Point& p, const Point& q)
    {
        return p.x<q.x;
    });

    tree.resize(n*4+10);

    for(i=0; i<n; i++)
    {
        last=-1, ret=0;
        query(1, 1, n, 1, arr[i].y);
        ans+=ret;
        update(1, 1, n, arr[i].x, arr[i].y);
    }

    printf("%lld", ans);
}

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함