페이지

레이블이 monotonous stack인 게시물을 표시합니다. 모든 게시물 표시
레이블이 monotonous stack인 게시물을 표시합니다. 모든 게시물 표시

3119번: OGRADA

https://www.acmicpc.net/problem/3119

높이가 h 이상인 나무판에 대해 남는 여부를 결정했다면 해당 나무판들은 최종적으로 남아있을 수 있다.
높이가 h 미만인 나무판들로 어떠한 울타리를 만들어도 높이가 h 이상인 나무판을 포함할 수 없기 때문이다.

그러므로 다음의 그리디 알고리즘이 가능하다.
1. 현재 남아있을지 결정해야 하는 나무판들의 높이 중 최댓값을 h라 한다.
2. 높이가 h인 나무판들에 대해 이미 남아 있기로 결정된 h보다 큰 나무판과 겹치는 부분을 제거한다. 높이가 h인 각 나무판마다 제거되지 않은 부분의 x 성분 최솟값을 si, 최댓값을 ei 라 하자.
3. i < j일 때 si < sj & ei < ej를 만족하도록 높이가 h인 나무판들을 최대한 많이 선별한다.
4. 선별된 나무판들을 최종적으로 남도록 결정한다.
5. 결정하지 못한 나무판들에 대해 1로 돌아가 처리한다.

2, 3번 과정은 map으로 처리 가능하다. 자세한 구현은 아래 소스를 참고하자.

시간복잡도는 $O(n\lg n)$

#include<cstdio>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
int n;
struct st { int x, w, h, idx; }a[100000];
vector<int> res;
vector<pair<int, int> > stk;
map<int, int> mp;
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d%d%d", &a[i].x, &a[i].w, &a[i].h), a[i].idx = i + 1;
    sort(a, a + n, [](st i, st j) {return i.h > j.h || i.h == j.h&&i.x < j.x; });
    for (int i = 0, s, e; i < n; i++) {
        auto it = mp.upper_bound(s = a[i].x);
        if (it != mp.begin() && s < (--it)->second) s = it->second;
        it = mp.upper_bound(e = a[i].x + a[i].w);
        if (it != mp.begin() && e <= (--it)->second) e = it->first;
        if (s < e && (stk.empty() || stk.back().second < e)) {
            while (!stk.empty() && stk.back().first == s) stk.pop_back(), res.pop_back();
            stk.push_back({ s,e });
            res.push_back(a[i].idx);
        }
        if (i == n - 1 || a[i].h^a[i + 1].h) {
            while (!stk.empty()) {
                s = stk.back().first;
                e = stk.back().second;
                it = mp.upper_bound(s);
                if (it != mp.begin() && s == prev(it, 1)->second) s = (--it)->first;
                while (it != mp.end() && it->first <= e) {
                    e = max(e, it->second);
                    mp.erase(it++);
                }
                mp[s] = e;
                stk.pop_back();
            }
        }
    }
    printf("%d\n", res.size());
    for (auto it : res) printf("%d ", it);
    return 0;
}

8885번: Pirate Chest

https://www.acmicpc.net/problem/8885

시간복잡도는 $O(n^2m)$

#include<cstdio>
#include<algorithm>
using namespace std;
int a, b, n, m, d[501][501], sz[501];
pair<int, int> stk[501][501];
long long res;
void update(int x, int y, int h) {
    x = min(x, a);
    y = min(y, b);
    int s = x*y;
    res = max(res, s*(h + (1LL * h*s - 1) / (n*m - s)));
}
void push(int i, int x, int y, int h) {
    while (stk[i][sz[i]].first > h) {
        update(x, y - stk[i][sz[i] - 1].second - 1, stk[i][sz[i]].first);
        update(y - stk[i][sz[i] - 1].second - 1, x, stk[i][sz[i]].first);
        sz[i]--;
    }
    stk[i][++sz[i]] = { h,y };
}
int main() {
    scanf("%d%d%d%d", &a, &b, &n, &m);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", d[i] + j);
            int x = 1e9;
            for (int k = i; k; k--) push(k, i - k + 1, j, x = min(x, d[k][j]));
        }
        for (int j = i; j; j--) push(j, i - j + 1, m + 1, 0), sz[j] = 0;
    }
    printf("%lld", res);
    return 0;
}

3015번: PATRIK

https://www.acmicpc.net/problem/3015

$O(n)$

입력으로 A[1..n]이 차례대로 주어진다.
monotonous stack를 이용해 A[1..i-1]의 단조 감소 수열 a[1..i-1]을 구해놓았다고 하자.
(이 수열은 i<j이고 A[i]<A[j]일 때, A[i]를 제거한 수열이다.)
1..i-1번째 사람 중 i번째 사람과 서로 볼 수 있는 사람은 a[1..i-1]에서 A[i]보다 작거나 같은 사람과 A[i]보다 큰 사람 중 키가 가장 작은 사람이다.

#include<cstdio>
int n, a[500001], c[500001], m, x;
long long r;
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &x);
        while (m > 0 && a[m] < x) r += c[m--];
        if (a[m] == x) r += c[m]++;
        else a[++m] = x, c[m] = 1;
        r += m > 1;
    }
    printf("%lld", r);
    return 0;
}

3346번: Mobile

https://www.acmicpc.net/problem/3346

$O(n)$

점들 중 x좌표가 같다면 y=0에 더 먼 점을 제외시킨다.
각 연속한 두 점을 지나고 y=0에 중심이 있는 원 중심의 x좌표가 증가하는 점들의 나열을 구하면 문제를 해결할 수 있다.
이러한 점들의 나열은 사전에 점들이 x좌표를 기준으로 정렬되어 있을 경우 monotonous stack을 이용해 O(n)에 구할 수 있다.

u-v를 점 u,v를 지나고 중심이 y=0에 있는 원,
f(u,v)를 이러한 원 중심의 x좌표라고 정의하자.
x좌표가 증가하는 순으로 세 점 a,b,c가 주어졌을 때
a-c 밖에 b가 있다 <-> f(a,c)>f(b,c)
이다.
이를 이용해 점들을 저장하는 스택을 준비해서
top이 원 (top-1)-(새로운 점) 밖에 있는 한 pop을 해주고 새로운 점을 추가하는 방식으로 처음 제시한 조건의 나열을 구할 수 있다.

문제의 조건에 따라 원의 중심은 선분 y=0 & 0<=x<=l에 있어야 한다.
(0,0)과 (l,0)에 가장 가까운 점 s,e를 구해서
중심 (0,0)과 s를 지나는 원, 중심 (l,0)과 e를 지나는 원, s~e 사이의 점들을 가지고 위 방법으로 구한 원들 중 최대 반지름이 답이다.

#include<cstdio>
#include<algorithm>
using namespace std;
const int MXN = 1e6;
struct point {
    double x, y;
}p[MXN], stk[MXN];
int n, l, top, s, e;
double r;
double crs(point i, point j) { return ((i.y*i.y - j.y*j.y) / (i.x - j.x) + i.x + j.x) / 2; }
int main() {
    scanf("%d%d", &n, &l);
    for (int i = 0; i < n; i++) {
        scanf("%lf%lf", &p[i].x, &p[i].y);
        p[i].y = abs(p[i].y);
        if (hypot(p[s].x, p[s].y)>hypot(p[i].x, p[i].y)) s = i;
        if (hypot(p[e].x - l, p[e].y) > hypot(p[i].x - l, p[i].y)) e = i;
    }
    r = max(hypot(p[s].x, p[s].y), hypot(p[e].x - l, p[e].y));
    stk[top] = p[s];
    for (int i = s + 1; i <= e; i++) if (stk[top].x<p[i].x || stk[top].y>p[i].y) {
        while (stk[top].x == p[i].x || top&&crs(stk[top - 1], p[i]) > crs(stk[top], p[i])) top--;
        stk[++top] = p[i];
    }
    for (int i = 0; i < top; i++) {
        point t = { crs(stk[i],stk[i + 1]),0 };
        r = max(r, hypot(t.x - stk[i].x, stk[i].y));
    }
    printf("%lf", r);
    return 0;
}

1725번: 히스토그램

https://www.acmicpc.net/problem/1725


$O(n)$

각 막대마다 자신을 시작으로 하는 최대 직사각형을 찾을 수 있는데 이러한 n개의 부분해들 중 최대값이 전체 문제의 해가 될 것이다.
이러한 최대 직사각형은 monotonous stack(이하 스택)를 이용하여 각각 amortized O(1)에 구할 수 있다.

막대를 입력받을 때마다 스택에 (x위치, 높이) 정보를 저장한다.
스택의 최근에 저장한 막대의 높이가 새로 입력받는 막대의 높이보다 작거나 같을 때까지 스택에서 pop을 하며 해당 막대를 시작으로 하는 최대 직사각형의 넓이를 구해준다.
이때 스택에는 막대들 높이가 오름차순으로 유지되므로 최대 직사각형의 밑변 길이는 해당 막대와 새로 추가하고자 하는 막대의 x좌표 차이가 될 것이다.
처음과 끝에 높이가 0인 더미 막대를 추가해주면 쉽게 구현할 수 있다.


#include<cstdio>
#define max(x,y) x>y?x:y
const int MXN = 1e5;
int n, a[MXN + 2], stk[MXN + 1], top = 1, r;
void push(int h) {
    while (a[stk[top - 1]] > a[h]) --top, r = max(r, a[stk[top]] * (h - 1 - stk[top - 1]));
    stk[top++] = h;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", a + i), push(i);
    push(n + 1);
    printf("%d", r);
    return 0;
}

6549번: 히스토그램에서 가장 큰 직사각형

https://www.acmicpc.net/problem/6549


$O(tn)$

각 막대마다 자신을 시작으로 하는 최대 직사각형을 찾을 수 있는데 이러한 n개의 부분해들 중 최대값이 전체 문제의 해가 될 것이다.
이러한 최대 직사각형은 monotonous stack(이하 스택)를 이용하여 각각 amortized O(1)에 구할 수 있다.

막대를 입력받을 때마다 스택에 (x위치, 높이) 정보를 저장한다.
스택의 최근에 저장한 막대의 높이가 새로 입력받는 막대의 높이보다 작거나 같을 때까지 스택에서 pop을 하며 해당 막대를 시작으로 하는 최대 직사각형의 넓이를 구해준다.
이때 스택에는 막대들 높이가 오름차순으로 유지되므로 최대 직사각형의 밑변 길이는 해당 막대와 새로 추가하고자 하는 막대의 x좌표 차이가 될 것이다.
처음과 끝에 높이가 0인 더미 막대를 추가해주면 쉽게 구현할 수 있다.


#include<cstdio>
#define max(x,y) x>y?x:y
const int MXN = 1e5;
int n, a[MXN + 2], stk[MXN + 1], top;
long long r;
void push(int h) {
    while (a[stk[top - 1]] > a[h]) --top, r = max(r, (long long)a[stk[top]] * (h - 1 - stk[top - 1]));
    stk[top++] = h;
}
int main() {
    while (scanf("%d", &n) && n) {
        top = 1; r = a[n + 1] = 0;
        for (int i = 1; i <= n; i++) scanf("%d", a + i), push(i);
        push(n + 1);
        printf("%lld\n", r);
    }
    return 0;
}

3357번: Monument

https://www.acmicpc.net/problem/3357


$O(pqr)$

다음 두 문제의 아이디어를 조합하면 풀 수 있다.
(1) http://codedoc.tistory.com/723
(2) http://codedoc.tistory.com/911

(x,y,z)상에 0 혹은 1이 주어졌을 때 1로만 이루어진 a*a*b꼴 직육면체 중 최대 4ab를 구해보자.
편의상 xy평면과 직육면체의 정사각형인 밑면과 평행하다고 하자.
푼 다음 직육면체를 적절히 돌려 나머지 방향에서도 똑같이 풀면 될 것이다.
먼저, 각 (x,y,z)마다 해당 지점을 끝점으로 하는 (1...x,1...y,z)상의 가장 큰 정사각형 한 변 길이를 구해놓는다. ... (2)
(x,y,1...i...z)에서 (x,y,i)의 최대 정사각형 한 변 길이를 a[i]라 하고 이들로 이루어진 히스토그램상에서 최대 직사각형 넓이를 구한다. ... (1)
이렇게 하면 (x,y,1...z)가 직육면체의 끝점이 되는 (1...x,1...y,1...z) 상의 직육면체 최대 ab가 구해진다.
모든 x,y에 대해 똑같이 풀고 최대 4ab를 구한다.


#include<cstdio>
#include<algorithm>
using namespace std;
int a[3], p[3] = { 0,1,2 }, pi[3], c[3], dp[151][151][151], stk[151], top, r;
char s[151][151][152];
int main() {
    scanf("%d%d%d", a + 1, a, a + 2);
    for (int i = 1; i <= a[0]; i++) for (int j = 1; j <= a[1]; j++) scanf("%s", s[i][j] + 1);
    do {
        for (int i = 0; i < 3; i++) pi[p[i]] = i;
        for (int i = 1; i <= a[p[0]]; i++) {
            for (int j = 1; j <= a[p[1]]; j++) {
                for (int k = 1; k <= a[p[2]]; k++) {
                    c[pi[0]] = i; c[pi[1]] = j; c[pi[2]] = k;
                    int n = s[c[0]][c[1]][c[2]] == 'N';
                    dp[i][j][k] = (min({ dp[i - 1][j][k], dp[i][j - 1][k], dp[i - 1][j - 1][k] }) + 1)*n;
                    while (dp[i][j][stk[top]]>dp[i][j][k]) r = max(r, (k - 1 - stk[top - 1])*dp[i][j][stk[top--]]);
                    stk[++top] = k;
                }
                while (top) r = max(r, (a[p[2]] - stk[top - 1])*dp[i][j][stk[top--]]);
            }
        }
    } while (next_permutation(p, p + 3));
    printf("%d", 4 * r);
    return 0;
}

11873번: 최대 직사각형

https://www.acmicpc.net/problem/11873


$O(tnm)$

http://codedoc.tistory.com/723의 아이디어를 이용하자.
i행의 각 열마다 위로 연속된 1의 개수를 위 문제에서 히스토그램 높이라 생각하고 최대직사각형 넓이를 구한다.
모든 행에서 이러한 방법을 적용했을 때 최대 직사각형 넓이를 찾는다.


#include<cstdio>
#include<algorithm>
using namespace std;
int n, m;
int main() {
    while (scanf("%d%d", &n, &m) && n) {
        int r = 0, h[1001] = { 0, }, stk[1001], top;
        for (int i = 1; i <= n; i++) {
            stk[top = 0] = 0;
            for (int j = 1, x; j <= m; j++) {
                scanf("%d", &x);
                h[j] = (h[j] + 1) * x;
                while (h[stk[top]] > h[j]) r = max(r, (j - stk[top - 1] - 1)*h[stk[top--]]);
                stk[++top] = j;
            }
            while (top) r = max(r, (m - stk[top - 1])*h[stk[top--]]);
        }
        printf("%d\n", r);
    }
    return 0;
}

2493번: 탑

https://www.acmicpc.net/problem/2493


$O(n)$

새로운 탑 정보를 스택에 추가할 때마다 기존 스택에 있는 현재 탑보다 작은 탑을 모두 pop시킨다.
탑을 추가할 때마다 이러한 작업을 한 뒤에 스택의 가장 위에 있는 탑 번호를 출력하고 스택에 현재 탑을 추가한다.
이러한 방식을 유지하면 탑이 높이를 기준으로 내림차순 정렬된다.


#include<cstdio>
int a[500000] = { 0x7fffffff }, stk[500000], top, n;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        while (a[stk[top]] < a[i]) top--;
        printf("%d ", stk[top]);
        stk[++top] = i;
    }
    return 0;
}