2017년 8월 9일 수요일

(Segment Tree + alpha) Codeforces #426 (Div. 1) B - The Bakery

<Segment Tree + alpha 문제 모음>
http://codeforces.com/blog/entry/22616

<Segment Tree + DP>
Codeforces #426 (Div. 1) B - The Bakery http://codeforces.com/contest/833/problem/B

<Segment Tree + Binary Search>
1321 군인 https://www.acmicpc.net/problem/1321
9426 중앙값 측정 https://www.acmicpc.net/problem/9426
1849 순열 https://www.acmicpc.net/problem/1849

<Segment Tree + Plane sweeping>
2185 직사각형의 합집합 https://www.acmicpc.net/problem/2185
3392 화성지도 https://www.acmicpc.net/problem/3392
7626 직사각형 https://www.acmicpc.net/problem/7626

<Segment Tree + Divide & conquer>
14727 퍼즐 자르기 https://www.acmicpc.net/problem/14727

1. <Segment Tree + DP>
- Codeforces #426 (Div. 1) B - The Bakery
$N$개의 케이크의 종류가 주어지고 $K$개의 박스가 있다.
케이크를 순서대로 묶어서 $K$개의 박스에 넣을 때 박스에 들어있는 케이크의 종류의 합을 
최대화 시키는 문제이다.
(박스에 들어있는 케이크의 종류의 갯수 : $T_{i}$이면 $max$ $\sum_{i=1}^{K} T_{i}$를 구해야한다.

풀이가 직관적으로 이해하기 어렵다.
DPDP인데 segment tree로만을 이용한 DP? 같은 느낌이다.

$K$가 1인 경우는 set을 이용하면 구할 수 있다.
$K$가 1보다 큰 경우에 segment tree를 이용한다.

#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int maxN = 35100;
int n, k;
int dp[55][maxN];
int a[maxN], pre[maxN], last[maxN];
int segt[1 << 17], lazy[1 << 17];
void build(int ki, int node, int l, int r) {
    lazy[node] = 0;
    if (l == r) {
        segt[node] = dp[ki][l];
        return;
    }
    int mid = (l + r) >> 1, nxt = node << 1;
    build(ki, nxt, l, mid);
    build(ki, nxt + 1, mid + 1, r);
    segt[node] = max(segt[nxt], segt[nxt + 1]);
}
void lazyprop(int node, int l, int r) {
    if (!lazy[node])
        return;
    segt[node] += lazy[node];
    if (l != r) {
        int nxt = node << 1;
        lazy[nxt] += lazy[node];
        lazy[nxt + 1+= lazy[node];
    }
    lazy[node] = 0;
}
void update(int node, int l, int r, int i, int j) {
    lazyprop(node, l, r);
    if (l>|| i>|| j<l)
        return;
    if (i <= l && j >= r) {
        lazy[node] = 1;
        lazyprop(node, l, r);
        return;
    }
    int mid = (l + r) >> 1, nxt = node << 1;
    update(nxt, l, mid, i, j);
    update(nxt + 1, mid + 1, r, i, j);
    segt[node] = max(segt[nxt], segt[nxt + 1]);
}
int query(int node, int l, int r, int i, int j) {
    lazyprop(node, l, r);
    if (l>|| i>|| j<l)
        return 0;
    if (i <= l && j >= r)
        return segt[node];
    int mid = (l + r) >> 1, nxt = node << 1;
    return max(query(nxt, l, mid, i, j), query(nxt + 1, mid + 1, r, i, j));
}
int main(int argc, char ** argv) {
    cin >> n >> k;
    for (int i = 0; i<n; i++) {
        cin >> a[i];
        if (last[a[i]])
            pre[i] = last[a[i]];        //이전의 index를 넣어줌
        last[a[i]] = i;
    }
    set<int> k0;
    for (int i = 0; i<n; i++) {
        k0.insert(a[i]);
        dp[0][i] = k0.size();
    }
    for (int i = 1; i<k; i++) {
        build(i - 110, n - 1);        //dp[i-1][j]를 세그먼트 트리 노드에 삽입
        for (int j = i; j<n; j++) {
            update(10, n - 1, pre[j], j - 1);        //a[j]와 같은 값이 나오는 이전값 ~ 
            dp[i][j] = max(dp[i - 1][j], query(10, n - 10, j));
        }
    }
    cout << dp[k - 1][n - 1<< '\n';
    return 0;
}
cs

2. <Segment Tree + Binary Search>
- 군인 
부대의 수가 줄어들거나 늘어날 수가 있다.
군인은 부대의 정원에 맞게 차례대로 들어간다.
해당 군인이 어디 부대에 있는지 쿼리를 처리하는 문제다.

세그먼트 트리로 처리할 수 있다.
update부분은 동일한데 query부분이 특이하다.
이분탐색을 세그먼트트리에 적용한것이다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
struct Segment {
    int size;
    vector<int> tree;
    void init(int N) { tree.resize(4 * N, 0); size = N; }
    int update(int idx, int val, int node, int nl, int nr) {
        if (idx < nl || idx > nr) return tree[node];
        if (nl == nr) return tree[node] += val;
        int mid = (nl + nr) >> 1;
        return tree[node] = update(idx, val, node * 2, nl, mid) +
            update(idx, val, node * 2 + 1, mid + 1, nr);
    }
    int update(int idx, int val) {
        return update(idx, val, 10size - 1);
    }
    int query(int val, int node, int nl, int nr) {
        if (nl == nr) return nl;
        int mid = (nl + nr) >> 1;
        if (tree[node * 2>= val) return query(val, node * 2, nl, mid);
        else return query(val - tree[node * 2], node * 2 + 1, mid + 1, nr);
    }
    int query(int val) {
        return query(val, 10size - 1);
    }
};
int N, M;
Segment seg;
int main() {
    scanf("%d"&N);
    seg.init(N);
    for (int n = 0, v;n < N;n++) {
        scanf("%d"&v);
        seg.update(n, v);
    }
    scanf("%d"&M);
    for (int m = 0;m < M;m++) {
        int a, b, c;
        scanf("%d"&a);
        if (a == 1) {
            scanf("%d%d"&b, &c);
            b--;
            seg.update(b, c);
        }
        else {
            scanf("%d"&b);
            printf("%d\n", seg.query(b) + 1);
        }
    }
    return 0;
}
cs

순열 
근본적인 풀이부터 좀 헤맸던 문제.
1부터 N까지 수열이 있는데 $i$보다 큰 수들의 개수가 저장되있는 A[$i$]가 주어진다.
역으로 원본 수열을 구하는 문제이다.

만약 원본수열의 아직 넣지 않은 인덱스들을 모아놓는 자료구조가 있다면
$i$는 ans[A[$i$] + 1]번째 인덱스로 들어간다. (그림 그려보면 나옴)
이 자료구조를 set, vector, map으로 구현해 보려고 했는데 실패해서 
segment tree + binary search를 이용하기로 했다.
풀고나서 이렇게 푼 사람 별로없을것 같아 뿌듯했는데 정해가 이 방법이다...
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
struct Segment {
    int Size;
    vector<int> tree;
    Segment(int s) :Size(s) { tree.resize(4 * s, 0); }
    int update(int val, int idx, int node, int nl, int nr) {
        if (idx < nl || idx > nr) return tree[node];
        if (nl == nr) return tree[node] += val;
        int mid = (nl + nr) >> 1;
        return tree[node] = update(val, idx, node * 2, nl, mid) + 
                            update(val, idx, node * 2 + 1, mid + 1, nr);
    }
    void update(int val, int idx) {
        update(val, idx, 10, Size - 1);
    }
    int query(int val, int node, int nl, int nr) {
        if (nl == nr) return nl;
        int mid = (nl + nr) >> 1;
        if (tree[node * 2>= val) return query(val, node * 2, nl, mid);
        else return query(val - tree[node * 2], node * 2 + 1, mid + 1, nr);
    }
    int query(int val) {
        return query(val, 10, Size - 1);
    }
};
int N;
int A[100001];    
int ans[100001];
int main() {
    scanf("%d"&N);
    for (int n = 1;n <= N;n++scanf("%d"&A[n]);
    Segment seg(N);
    for (int n = 1;n <= N;n++)
        seg.update(1, n - 1);
    for (int n = 1;n <= N;n++) {
        int idx = seg.query(A[n] + 1);
        ans[idx + 1= n;
        seg.update(-1, idx);
    }
    for (int n = 1;n <= N;n++printf("%d\n", ans[n]);
    return 0;
}
cs

중앙값 측정
multiset 2개로 풀 수 있긴하나 솔직히 복잡하다.
이 문제도 segment tree + binary search로 해결할 수 있다.

3. <Segment Tree + Plane Sweeping>
구간 [i, j]에서 0보다 큰값을 가지고있는 노드들의 갯수를 세는 방식도 잘 기억하자!

- 직사각형의 합집합
겹칠 수 있는 직사각형들이 주어지고 이 직사각형의 합집합의 둘레를 구하는 문제다.

y축과 평행한 선을 기준으로 삼고 맨 왼쪽부터 오른쪽으로 가면서 훑는 plane sweeping 작업을 한다.
각 직사각형의 y가 작은 x선, x가 큰 y선에 각각 +1, -1을 넣으며 segment treeupdate하는 형식이다.
y축 기준으로, x축 기준으로 결과를 저장하면 된다.

<주의사항>
- 정렬을 X가 작은순서 그다음으로 +1인 순서로 해야한다.
다음과 같이 빨강직사각형의 끝선(1)과 파랑직사각형의 시작선(2)의 x축이 동일하다고 가정해보자.
- (2)가 먼저 update되고 (1)이 나중에 됨
우리가 구하고자 하는 왼쪽에서 봤을때의 둘레의 합이 구해질 것이다. <result = (1+ (2) - (1)>
- (1)이 먼저 update되고 (2)가 나중에 됨
(1)이 두번 더해질 것이다. <result = (1+ (2)>

- segment treeupdate시킬 때 구간의 마지막값 -1을 해서 넣어줘야한다.
이건 update구현에 따라 다를 수 있는데 정렬시키기 전에 -1을 하는 실수를 하지 말자.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAX 20001
typedef pair<pair<intpair<intint>>int> P;
struct Segment {
    vector<int> tree, cnt;
    Segment(){ tree.resize(4 * MAX, 0), cnt.resize(4 * MAX, 0); }
    void update(int val, int l, int r, int node, int nl, int nr) {
        if (r < nl || l > nr) return;
        if (l <= nl && nr <= r) tree[node] += val;
        else {
            int mid = (nl + nr) >> 1;
            update(val, l, r, node * 2, nl, mid);
            update(val, l, r, node * 2 + 1, mid + 1, nr);
        }
        if (tree[node]) cnt[node] = nr - nl + 1;
        else {
            if (nl == nr) cnt[node] = 0;
            else cnt[node] = cnt[node * 2+ cnt[node * 2 + 1];
        }
    }
    void update(int val, int l, int r) {
        update(val, l, r, 10, MAX - 1);
    }
    int query() {
        return cnt[1];
    }
};
int N, ans;    
int rect[5000][4];
vector<P> adj;
void get() {
    Segment seg;
    sort(adj.begin(), adj.end(), [](P a, P b)->bool {
        if(a.first.first == b.first.first)
            return a.second > b.second;
        return a.first.first < b.first.first;
    });
    for (auto n : adj) {
        int fir = seg.query();
        seg.update(n.second, n.first.second.first, n.first.second.second - 1);
        int sec = seg.query();
        ans += abs(fir - sec);
    }
    adj.clear();
}
int main() {
    scanf("%d"&N);
    for (int n = 0;n < N;n++) {
        for (int m = 0;m < 4;m++)
            scanf("%d"&rect[n][m]), rect[n][m] += 10000;
        if (rect[n][0> rect[n][2]) swap(rect[n][0], rect[n][2]);
        if (rect[n][1> rect[n][3]) swap(rect[n][1], rect[n][3]);
    }
    for (int n = 0;n < N;n++) {
        adj.push_back({ { rect[n][0], { rect[n][1],rect[n][3]} }, 1 });
        adj.push_back({ { rect[n][2], { rect[n][1],rect[n][3]} }, -1 });
    }
    get();
    for (int n = 0;n < N;n++) {
        adj.push_back({ { rect[n][1], {rect[n][0],rect[n][2]}}, 1 });
        adj.push_back({ { rect[n][3], {rect[n][0],rect[n][2]}}, -1});
    }
    get();
    printf("%d\n", ans);
    return 0;
}
cs
화성지도
위에선 둘레를 구했다면 이번엔 면적을 구하는 문제이다. 좀더 쉬운듯하다.

비슷한 방식인데 이전 x값과 현재 x값의 차이를 전체 구간에서 1이상인 점들의 갯수와 곱해주면 된다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define MAX 30001
typedef pair<pair<intint>pair<intint>> P;
struct Segment {
    vector<int> tree, cnt;
    Segment() { tree.resize(4 * MAX, 0), cnt.resize(4 * MAX, 0); }
    void update(int val, int l, int r, int node, int nl, int nr) {
        if (r < nl || l > nr) return;
        if (l <= nl && nr <= r) tree[node] += val;
        else {
            int mid = (nl + nr) >> 1;
            update(val, l, r, node * 2, nl, mid);
            update(val, l, r, node * 2 + 1, mid + 1, nr);
        }
        if (tree[node]) cnt[node] = nr - nl + 1;
        else {
            if (nl == nr) cnt[node] = 0;
            else cnt[node] = cnt[node * 2+ cnt[node * 2 + 1];
        }
    }
    void update(int val, int l, int r) {
        update(val, l, r, 10, MAX - 1);
    }
    int query() {
        return cnt[1];
    }
};
int N;    
vector<P> rect;
int main() {
    Segment seg;
    scanf("%d"&N);
    for (int n = 0;n < N;n++) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d"&x1, &y1, &x2, &y2);
        rect.push_back({ { x1,1 },{ y1,y2 - 1 } });
        rect.push_back({ { x2,-1 }, { y1,y2 - 1 } });
    }
    sort(rect.begin(), rect.end());
    int ans = 0, prev = -1;
    for (auto n : rect) {
        int curr = n.first.first;
        if (prev != -1) {
            int diff = curr - prev;
            ans += diff*seg.query();
        }
        seg.update(n.first.second, n.second.first, n.second.second);
        prev = curr;
    }
    printf("%d\n", ans);
    return 0;
}
cs
직사각형
화성지도와 동일한 문제이나 좌표가 $10^{9}$까지 들어오기때문에 좌표압축이 필요하다.
알고리즘은 동일하다.
#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
using namespace std;
vector<int> ypos;
struct Segment {
    int size;
    vector<int> tree, cnt;
    Segment() {}
    Segment(int s) :size(s) { tree.resize(4 * size0); cnt.resize(4 * size0); }
    void update(int val, int l, int r, int node, int nl, int nr) {
        if (nr < l || nl > r) return;
        if (l <= nl && nr <= r) tree[node] += val;
        else {
            int mid = (nl + nr) >> 1;
            update(val, l, r, node * 2, nl, mid);
            update(val, l, r, node * 2 + 1, mid + 1, nr);
        }
        if (tree[node]) cnt[node] = ypos[nr + 1- ypos[nl];
        else {
            if (nl == nr) cnt[node] = 0;
            else cnt[node] = cnt[node * 2+ cnt[node * 2 + 1];
        }
    }
    void update(int val, int l, int r) {
        update(val, l, r, 10size - 1);
    }
    int query() { return cnt[1]; }
};
struct Line {
    int x, y1, y2;
    int c;
    Line() {}
    Line(int xx, int yy1, int yy2, int cc) :x(xx), y1(yy1), y2(yy2), c(cc) {}
};
int N;
vector<int> save_y;
vector<Line> line;
map<intint> mat;
int main() {
    scanf("%d"&N);
    for (int n = 0;n < N;n++) {
        int x1, x2, y1, y2;
        scanf("%d%d%d%d"&x1, &x2, &y1, &y2);
        line.emplace_back(Line(x1, y1, y2, 1));
        line.emplace_back(Line(x2, y1, y2, -1));
        save_y.emplace_back(y1);
        save_y.emplace_back(y2);
    }
    sort(line.begin(), line.end(), [](Line &a, Line &b)->bool {
        return a.x < b.x;
    });
    sort(save_y.begin(), save_y.end());
    save_y.erase(unique(save_y.begin(), save_y.end()), save_y.end());
    for (int n = 0;n < save_y.size();++n) {
        mat[save_y[n]] = n;
        ypos.emplace_back(save_y[n]);
    }
    Segment seg(save_y.size());
    long long ans = 0;
    for (int n = 0;n < line.size();++n) {
        if (n) ans += 1LL * (line[n].x - line[n - 1].x)*seg.query();
        int y1 = line[n].y1, y2 = line[n].y2;
        seg.update(line[n].c, mat[line[n].y1], mat[line[n].y2] -  1);
    }
    printf("%lld\n", ans);
    return 0;    
}
cs
4. <Segment Tree + Divide & conquer>
- 퍼즐 자르기
상당히 유명한 문제다.
히스토그램, 히스토그램에서 가장 큰 직사각형 등의 동일 문제가 있다.

분할정복과 segment tree를 접목시켜 풀때는 segment tree에 최소높이의 index를 넣어준다.
구간 [left, right]에서 [left, index - 1], [index + 1, right]으로 분할정복해 나가는것이다.

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int N;
ll height[100001];
struct Segment {
    vector<int> tree;
    Segment(int S) { tree.resize(4 * S, 0); }
    void init(int node, int nl, int nr) {
        if (nl == nr) {
            tree[node] = nl;
        }
        else {
            int mid = (nl + nr) >> 1;
            init(node * 2, nl, mid);
            init(node * 2 + 1, mid + 1, nr);
            if (height[tree[node * 2]] < height[tree[node * 2 + 1]])
                tree[node] = tree[node * 2];
            else
                tree[node] = tree[node * 2 + 1];
        }
    }
    void init() {
        init(10, N - 1);
    }
    int query(int l, int r, int node, int nl, int nr) {
        if (nr < l || r < nl) return -1;
        if (l <= nl && nr <= r) return tree[node];
        int mid = (nl + nr) >> 1;
        int a = query(l, r, node * 2, nl, mid);
        int b = query(l, r, node * 2 + 1, mid + 1, nr);
        if (a == -1return b;
        if (b == -1return a;
        if (height[a] < height[b]) return a;
        else return b;
    }
    long long solve(int l, int r) {
        int idx = query(l, r, 10, N - 1);
        long long ret = (long long)(r - l + 1* height[idx];
        if (l <= idx - 1) ret = max(ret, solve(l, idx - 1));
        if (idx + 1 <= r) ret = max(ret, solve(idx + 1, r));
        return ret;
    }
};
int main() {
    scanf("%d"&N);
    Segment seg(N);
    for (int n = 0;n < N;n++scanf("%lld"&height[n]);
    seg.init();
    printf("%lld\n", seg.solve(0, N - 1));
    return 0;
}
cs

댓글 없음:

댓글 쓰기