티스토리 뷰

문제 풀이

[BOJ 8889] 등고선 지도

RiKang 2018.01.11 13:25

 2012 ACM-ICPC Daejeon regional 의 기출문제입니다. 백준 8889 에서 채점할 수 있습니다.


요약 )


 1) x, y 좌표의 최대값은 10억이지만, 갯수는 200만개 이하이기 때문에 좌표 압축을 통해 각각의 좌표를 1 ~ 200만으로 바꿀 수 있습니다.


 2) 최대 높이인 영역이 꼭지점을 하나 이상 포함하기 때문에, 모든 꼭지점의 높이 중 최대 높이만 구해도 전체 좌표 중 최대 높이가 됩니다.


 3) 특정 y좌표 (y1) 에 대한 segment tree를 통해, y좌표가 y1인 꼭지점(x1,y1)의 높이를 구할 수 있습니다.

segment tree 는 아래의 식이 성립하도록 설정해야 합니다.

0 ~ x1위치의 총합 = 좌표 (x1,y1) 꼭지점의 높이

이는 y = y1직선과 등고선의 교차점을 생각해 보면 설계할 수 있습니다.

등고선과 만남이 시작되는 위치에 +1, 만남이 끝나는 위치에 -1을 하는 식입니다.



 4) 각각의 y좌표에 대한 segment tree를 모두 저장할 필요없이, y=0일 때의 segment tree를 구한 다음, y=0 -> 200만으로 진행시켜 나가면서 트리가 갱신되는 부분만 고치면 됩니다. 갱신되는 부분이 어디인지는, 등고선 정보를 통해 알 수 있습니다.


위의 과정을 통하면 M = N*K <= 200만 이라고 할 때, O(M log M)이 가능합니다.


입력 & 좌표 압축 )


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
32
33
// code by RiKang, weeklyps.com
 
#include <bits/stdc++.h>
using namespace::std;
 
int n;
int a[20001];
int xy[20001][105][2];
int v[2000001];
int vm;
 
void input(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        for(int j=0;j<a[i];j++)
            scanf("%d %d",&xy[i][j][0],&xy[i][j][1]);
    }
    vm=0;
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        v[vm++]=xy[i][j][0];
    sort(v,v+vm);
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        xy[i][j][0= lower_bound(v,v+vm,xy[i][j][0])-v;
    
    vm=0;
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        v[vm++]=xy[i][j][1];
    sort(v,v+vm);
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        xy[i][j][1= lower_bound(v,v+vm,xy[i][j][1])-v;
}

cs


 위와 같이 x좌표들을 v 배열에 모아서 정렬한 다음, 이분 검색을 통해 좌표를 압축합니다. 제일 작은 x좌표는 0, 그다음 작은 x좌표는 1, ..., 제일 큰 x좌표 = vm 으로 바뀝니다. vm <= N*K = 20000*100 이므로 200만이하로 압축되었습니다.


쿼리 정리 )


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
// code by RiKang, weeklyps.com
 
#include <bits/stdc++.h>
using namespace::std;
 
int n;
int a[20001];
int xy[20001][105][2];
int vm;
 
vector<pair<int,int> > q[2000001];
 
void make_query(int x1,int y1,int x2,int y2){
    if(y1==y2){
        q[y1].push_back(make_pair(x1+1,x2+1));
    }
}
 
void input(){
    for(int i=0;i<=2000000;i++) q[i].clear();
    for(int i=0;i<n;i++){
        xy[i][a[i]][0]=xy[i][0][0],xy[i][a[i]][1]=xy[i][0][1];
        for(int j=0;j<a[i];j++) make_query(xy[i][j][0],xy[i][j][1],xy[i][j+1][0],xy[i][j+1][1]);
    }
}
cs


 각각의 y좌표에 대한 쿼리를 정리합니다.

 y1-1 에 대한 segment tree 가 존재할 때 이 것을 y1에 대한 segment tree로 바꾸려면 무엇이 필요한지를 찾아야 합니다. 이 구현에서는 y1 좌표에 있는 가로(-) 방향 간선의 정보를 저장하였습니다. 이후에 이 정보로 segment tree 를 갱신할 것입니다.

 세로 방향 간선의 경우엔 y = y1-1에서나 y = y1에서나 똑같기 때문에 업데이트할 부분이 없으므로 저장하지 않았습니다.


쿼리 처리 )


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// code by RiKang, weeklyps.com
 
#include <bits/stdc++.h>
using namespace::std;
 
vector<pair<int,int> > q[2000001];
 
void process(){
    int ans=0;
    init(2000000);
    for(int i=0;i<=2000000;i++){
        for(int j=0;j<q[i].size();j++){
            update(q[i][j].first,1);
            update(q[i][j].second,-1);
        }
        for(int j=0;j<q[i].size();j++)
            ans=max(ans,query(1,q[i][j].first,1,1,tree_n));
    }
    printf("%d\n",ans);
}
cs


 y = i 에 있는 가로(-) 방향 간선을 통해 등고선의 시작지점 & 끝지점을 업데이트 한 후, 꼭지점의 높이들을 찾아 답을 갱신합니다. 반시계 방향으로 입력이 주어지기 때문에 위와 같이 간단하게 업데이트할 수 있습니다.


최종 코드 )


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// code by RiKang, weeklyps.com
// 2012 ACM-ICPC Daejeon regional - B
// BOJ 8889 등고선 지도
 
#include <bits/stdc++.h>
using namespace::std;
 
int n;
int a[20001];
int xy[20001][105][2];
int v[2000001];
int vm;
 
vector<pair<int,int> > q[2000001];
 
// Segment tree 시작
int tree_n, tree[4294304];
void init(int n1){  // 초기화 : 아래에 깔리는 노드가 n1개
    tree_n=1;
    while(tree_n<n1){tree_n*=2;}
    for(int i=0;i<=2*tree_n;i++){tree[i]=0;}
}
void update(int po,int v1){  // po 번째값 업데이트
    po+=tree_n-1;
    tree[po]+=v1;
    while(po>1){
        po=po/2;
        tree[po]=tree[2*po]+tree[2*po+1];
    }
}
int query(int ql,int qr,int po,int l,int r){  // a~b구간, po노드가 ㅣ~r에 대응  => po=1  l=1  r=tree_n
    if(r<ql || l>qr) return 0;
    if(ql<=&& r<=qr) return tree[po];
    return query(ql,qr,2*po,l,(l+r)/2)+query(ql,qr,2*po+1,(l+r)/2+1,r);
}
// Segment tree 끝
 
void make_query(int x1,int y1,int x2,int y2){
    if(y1==y2){
        q[y1].push_back(make_pair(x1+1,x2+1));
    }
}
 
void input(){
    int n;
    for(int i=0;i<=2000000;i++) q[i].clear();
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        for(int j=0;j<a[i];j++)
            scanf("%d %d",&xy[i][j][0],&xy[i][j][1]);
    }
    vm=0;
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        v[vm++]=xy[i][j][0];
    sort(v,v+vm);
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        xy[i][j][0= lower_bound(v,v+vm,xy[i][j][0])-v;
    
    vm=0;
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        v[vm++]=xy[i][j][1];
    sort(v,v+vm);
    for(int i=0; i<n; i++for(int j=0; j<a[i]; j++)
        xy[i][j][1= lower_bound(v,v+vm,xy[i][j][1])-v;
        
    for(int i=0;i<n;i++){
        xy[i][a[i]][0]=xy[i][0][0],xy[i][a[i]][1]=xy[i][0][1];
        for(int j=0;j<a[i];j++) make_query(xy[i][j][0],xy[i][j][1],xy[i][j+1][0],xy[i][j+1][1]);
    }
}
 
void process(){
    int ans=0;
    init(2000000);
    for(int i=0;i<=2000000;i++){
        for(int j=0;j<q[i].size();j++){
            update(q[i][j].first,1);
            update(q[i][j].second,-1);
        }
        for(int j=0;j<q[i].size();j++)
            ans=max(ans,query(1,q[i][j].first,1,1,tree_n));
    }
    printf("%d\n",ans);
}
 
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        input();
        process();
    }
    return 0;
}
cs


'문제 풀이' 카테고리의 다른 글

[BOJ 8899] Square Annulus  (0) 2018.01.11
[BOJ 8890] Critical 3-Path  (0) 2018.01.11
[BOJ 8889] 등고선 지도  (0) 2018.01.11
[BOJ 8893] Pandora  (0) 2018.01.11
[BOJ 8894] Pattern Lock  (0) 2018.01.11
[BOJ 8891] 점 숫자  (0) 2018.01.10