[중급 알고리즘] 분할 정복(도전)

2022. 5. 23. 14:12TIL💡/Algorithms

 

💚1933 스카이라인

N개의 직사각형 건물이 있을 때 스카이라인을 구하는 문제

 

스카이라인을 차례대로 left x, h, right x 우선순위에 대한 오름차순으로 정렬한다.

이래야만 추후 점들을 대체하는 작업이 순조롭게 이루어진다.

 

이 문제의 핵심은 두 개의 스카이라인이 교차할 때 높이를 어떻게 결정할 것인가이다. 정답은 교차 시 더 높은 h값을 선택해야 한다.(max 사용)

그리고 추가적으로 동일한 x일 때, 동일한 h일 때에 대한 경우도 고려해야 한다.

가장 어려웠던 점이 대체 후에 동일한 경우까지 따져야한다는 점이 어려웠다.

#include <iostream>
#include <vector>
#include <tuple>
#include <algorithm>
using namespace std;
using Result = vector<pair<int,int>>;
struct Building {
    int left, right, height;
};
// first : x, second : h
void append(Result &ans, pair<int, int> p) {
    int n = ans.size();
    if(n > 0) {
        // h값이 동일한 경우 추가 X
        if(ans[n - 1].second == p.second) {
            return;
        }
        // x값이 동일한 경우 더 h값이 큰 걸로 대체
        if(ans[n - 1].first == p.first) {
            ans[n - 1].second = p.second;
            // 만약 교체 후 그보다 이전의 h값과 동일하다면 스카이라인 변경이 없으므로 제거
            if(n > 1 && ans[n - 2].second == ans[n - 1].second) {
                ans.pop_back();
            }
            return;
        }
    }
    ans.push_back(p);
}
Result merge(Result &l, Result &r) {
    Result ans;
    // 왼쪽 스카이라인의 현재 높이
    int h1 = 0;
    // 오른쪽 스카이라인의 현재 높이
    int h2 = 0;
    int i = 0;
    int j = 0;
    while(i < l.size() && j < r.size()) {
        auto &u = l[i];
        auto &v = r[j];
        // 등호 처리 중요
        // 만약 u.first == v.first면, 정렬 우선순위상 v보다 u의 height가 작거나 right가 작다는 의미가 내포
        // 따라서 u가 먼저 append되는 것이 맞다.
        if(u.first <= v.first) {
            int x = u.first;
            h1 = u.second;
            int h = max(h1, h2);
            append(ans, make_pair(x, h));
            i++;
        }
        else {
            int x = v.first;
            h2 = v.second;
            // 더 큰 높이로 선택
            // 만약 높이가 동일한 점이 있다면
            int h = max(h1, h2);
            append(ans, make_pair(x,h));
            j++;
        }
    }

    while(i < l.size()) {
        int x = l[i].first;
        h1 = l[i].second;
        int h = max(h1, h2);
        append(ans, make_pair(x, h));
        i++;
    }

    while(j < r.size()) {
        int x = r[j].first;
        h2 = r[j].second;
        int h = max(h1, h2);
        append(ans, make_pair(x, h));
        j++;
    }

    return ans;

}
Result go(vector<Building> &a, int start, int end) {
    if(start == end) {
        Result ans = {
                {a[start].left, a[start].height},
                {a[start].right, 0}
        };
        return ans;
    }

    int mid = (start + end) / 2;
    // left and right
    auto l = go(a, start, mid);
    auto r = go(a, mid + 1, end);

    return merge(l, r);
}

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

    cin >> n;
    vector<Building> a(n);

    for(int i = 0;i < n;i++) {
        cin >> a[i].left >> a[i].height >> a[i].right;
    }

    // left, height, right 순으로 오름차순 정렬
    sort(a.begin(), a.end(), [](Building &u, Building &v) {
        return make_tuple(u.left, u.height, u.right) < make_tuple(v.left, v.height, v.right);
    });

    auto ans = go(a, 0, n - 1);

    for(auto &p : ans) {
        cout << p.first << ' ' << p.second << ' ';
    }
    cout << '\n';
    return 0;
}

💚2261 가장 가까운 두 점

2차원 평면 위의 N개의 점 중에서 가장 가까운 두 점을 찾는 문제

2 <= N <= 100,000

모든 점의 쌍을 조사하면 O(N^2)이 걸린다. -> 시간 초과

 

먼저 점을 X 좌표가 증가하는 순으로 정렬

중간에 있는 점을 찾는다.

중간에 있는 점을 기준으로 왼쪽과 오른쪽으로 나눈다.

- 왼쪽: PL

- 오른쪽: PR

 

- PL에서 가장 가까운 두점 dL(왼쪽에서의 최솟값)

- PR에서 가장 가까운 두점 dR(오른쪽에서의 최솟값)

 

d = min(dL, dR)이라고 했을 때

가운데 점으로부터 -d, +d만큼 떨어진 곳에서 가장 가까운 두 점을 찾아야 한다.

 

만약 -d, +d 구간에 지나치게 점이 많으면 비효율적이다.

그런데 예를 들어 어떤 점의 좌표가 (x,y)였다면 거리를 조사해야하는 y좌표 범위는 [y-d, y+d]이다. 

d가 최솟값이라는 전제 하에 이 범위에 들어있는 점의 개수는 최대 6개이다.

 

각각의 단계마다 회색 영역에 있는 점을 y좌표 순으로 정렬해야 한다. 이렇게 하면 전체 시간 복잡도 O(N(logN)^2)

하지만 만약 미리 점을 y순으로 정렬해놓고 상대적인 순서를 유지한 채로 나눈면 매번 정렬을 하지 않아도 된다. 

이렇게 된다면 전체 시간 복잡도 O(NlogN)

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
struct Point {
    int x, y;
};

int dist(Point p1, Point p2) {
    return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}
bool cmp_x(const Point &u, const Point &v) {
    return make_pair(u.x, u.y) < make_pair(v.x, v.y);
}
bool cmp_y(const Point&u, const Point &v) {
    return make_pair(u.y, u.x) < make_pair(v.y, v.x);
}
int brute_force(vector<Point> &a, int start, int end) {
    int ans = -1;
    for(int i = start;i < end;i++) {
        for(int j = i + 1;j <= end;j++) {
            int d = dist(a[i], a[j]);
            if(ans == -1 || ans > d) {
                ans = d;
            }
        }
    }
    return ans;
}
int closest(vector<Point> &a, int start, int end) {
    int n = end - start + 1;
    if(n <= 3) {
        return brute_force(a, start, end);
    }

    int mid = (start + end) / 2;
    int left = closest(a, start , mid);
    int right = closest(a, mid + 1, end);
    int ans = min(left, right);
    // 회색 영역
    vector<Point> b;
    // x값을 기준으로 회색 영역 정하기
    for(int i = start;i <= end;i++) {
        int d = a[i].x - a[mid].x;
        if(d * d < ans) {
            b.push_back(a[i]);
        }
    }

    // 회색 영역 내의 점들 중 y값을 기준으로 왼쪽, 오른쪽에서 구한 최소의 거리보다 작은 거리 구하기
    sort(b.begin(), b.end(), cmp_y);

    int m = b.size();
    for(int i = 0;i < m - 1;i++) {
        for(int j = i + 1;j < m;j++) {
            int y = b[j].y - b[i].y;
            if(y * y < ans) {
                int d = dist(b[i], b[j]);
                if(d < ans) {
                    ans = d;
                }
            }
            else {
                break;
            }
        }
    }

    return ans;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<Point> a(n);
    for(int i = 0;i < n;i++) {
        int x, y;
        cin >> x >> y;
        a[i] = {x, y};
    }

    sort(a.begin(), a.end(), cmp_x);
    cout << closest(a, 0, n - 1) << '\n';
    return 0;

}

x로 정렬했다가, y로 정렬했다가 왔다갔다하면 비효율적이다.

그래서 x와 y로 정렬한 배열 따로 둔다.

x는 분할 정복으로 회색영역을 정할 때, y는 회색 영역 내에서 최단 거리를 구하는 용도로 쓴다.

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;
struct Point {
    int x, y;
};

int dist(Point p1, Point p2) {
    return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
}
bool cmp_x(const Point &u, const Point &v) {
    return make_pair(u.x, u.y) < make_pair(v.x, v.y);
}

bool cmp_xe(const Point &u, const Point &v) {
    return make_pair(u.x, u.y) <= make_pair(v.x, v.y);
}

bool cmp_y(const Point&u, const Point &v) {
    return make_pair(u.y, u.x) < make_pair(v.y, v.x);
}
int brute_force(vector<Point> &a, int start, int end) {
    int ans = -1;
    for(int i = start;i < end;i++) {
        for(int j = i + 1;j <= end;j++) {
            int d = dist(a[i], a[j]);
            if(ans == -1 || ans > d) {
                ans = d;
            }
        }
    }
    return ans;
}
int closest(vector<Point> &ax, vector<Point> &ay, int start, int end) {
    int n = end - start + 1;
    if(n <= 3) {
        return brute_force(ax, start, end);
    }

    int mid = (start + end) / 2;

    Point mid_p = ax[mid];
    vector<Point> ayl, ayr;
    for(Point p : ay) {
        if(cmp_xe(p, mid_p)) {
            ayl.push_back(p);
        }
        else {
            ayr.push_back(p);
        }
    }

    int left = closest(ax, ayl, start , mid);
    int right = closest(ax, ayr, mid + 1, end);
    int ans = min(left, right);
    // 회색 영역
    vector<Point> b;
    // x값을 기준으로 회색 영역 정하기
    for(Point p : ay) {
        int d = p.x - mid_p.x;
        if(d * d < ans) {
            b.push_back(p);
        }
    }

    int m = b.size();
    for(int i = 0;i < m - 1;i++) {
        for(int j = i + 1;j < m;j++) {
            int y = b[j].y - b[i].y;
            if(y * y < ans) {
                int d = dist(b[i], b[j]);
                if(d < ans) {
                    ans = d;
                }
            }
            else {
                break;
            }
        }
    }

    return ans;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin >> n;
    vector<Point> ax(n);
    vector<Point> ay(n);
    for(int i = 0;i < n;i++) {
        int x, y;
        cin >> x >> y;
        ax[i] = {x, y};
        ay[i] = {x, y};
    }

    sort(ax.begin(), ax.end(), cmp_x);
    sort(ay.begin(), ay.end(), cmp_y);

    cout << closest(ax, ay, 0, n - 1) << '\n';
    return 0;

}

'TIL💡 > Algorithms' 카테고리의 다른 글

[네트워크] STP  (0) 2022.05.23
[중급 알고리즘] 이분 탐색  (0) 2022.05.23
[중급 알고리즘] 분할 정복(연습)  (0) 2022.05.18
[중급 알고리즘] 그리디(도전)  (0) 2022.05.17
[중급 알고리즘] 그리디 (연습)  (0) 2022.05.16