ABOUT ME

한양대학교 알고리즘 동아리 ALOHA 소속의 hibye1217가 쓰는 동아리 탐방기 ALOHA (한양대학교) / SHARC (선린인터넷고등학교)

Today
Yesterday
Total
  • 중급반 3주차 풀이 - 이진 탐색, 파라메트릭 서치, 두 포인터
    ALOHA - 2022/ALOHA - 중급반 (2022 1학기) 2022. 5. 4. 00:21

    A. [11053] 가장 긴 증가하는 부분 수열 (Silver II)

    더보기

    DPi:= i번째 원소로 끝나는 LIS 로 정의하면,

    DPi=maxAj<AiDPj+1로 점화식을 세울 수 있습니다.

    초기값은 DPi=1이고, 문제의 답은 max1iNDPi입니다.

    int arr[1020], dp[1020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	for (int i = 1; i <= n; i++){
    		dp[i] = 1;
    		for (int j = 1; j <= i; j++){
    			if (arr[j] >= arr[i]){ continue; }
    			dp[i] = max(dp[i], dp[j]+1);
    		}
    	}
    	cout << *max_element(dp+1, dp+n+1);
    }

    B. [2003] 수들의 합 2 (Silver III)

    더보기

    두 포인터 p1과 p2를 놓고, [p1, p2) 구간의 합을 저장하면서 포인터를 잘 움직여봅시다.

     

    만약 현재 합이 M 이하라면 p2를 오른쪽으로 옮겨줘야 하고,

    그렇지 않으면 p1을 오른쪽으로 옮겨줘야 합니다.

     

    문제의 답은 합이 정확히 M이 된 횟수이며, 시간복잡도는 O(N)입니다.

     

    + Ai가 자연수이기 때문에 가능합니다. 그냥 정수라면 사용할 수 없습니다.

     

    1. [1920] 수 찾기 (Silver IV)

    더보기

    수열 A와 찾는 수 X가 주어질 때, 이를 이진 탐색으로 찾아주면 됩니다.

    lower_bound를 사용한다면 X 이상인 위치를 찾아줄테니, 그 위치에 있는 수가 X인지 확인해주면 됩니다.

     

    주의할 점: X가 수열의 모든 원소보다 크다면 lower_bound가 N+1번 위치를 가리키게 됩니다.

    근데 이걸 생각 못한 채로 그냥 Indexing을 해도 웬만해서는 답이 잘 나오지만...

    [-6, -4, -2]에서 0을 찾으려 하는 순간 잘못된 답이 나오는 걸 볼 수 있습니다.

    int arr[100020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	sort(arr+1, arr+n+1);
    	int q; cin >> q; while (q--){
    		int x; cin >> x;
    		auto idx = lower_bound(arr+1, arr+n+1, x) - arr;
    		if (idx > n){ cout << 0 << endl; continue; }
    		cout << (arr[idx] == x) << endl;
    	}
    }
    더보기

    STL이 존재하는데에는 이유가 있습니다. 신나게 set을 박아주면 됩니다.

    set<int> s;
    
    void Main(){
    	int n; cin >> n;
    	while (n--){ int x; cin >> x; s.insert(x); }
    	int q; cin >> q;
    	while (q--){ int x; cin >> x; cout << s.count(x) << endl; }
    }

     

    2. [1654] 랜선 자르기 (Silver III)

    더보기

    문제의 답이 X라고 하면, X 이하의 길이를 가지는 랜선은 만들 수 있지만, X 초과의 길이를 가지는 랜선은 만들 수 없기에, 이 문제를 파라메트릭 서치로 풀 수 있는 길을 만들어줍니다.

     

    그러니, 길이가 x인 랜선을 N개 이상 만들 수 있는지 판별하는 함수를 만들어봅시다.

    어떤 랜선 a에 대해, 길이가 x인 랜선은 a/x개 만들 수 있습니다.

    그러니, 랜선의 배열을 A라고 하면 길이가 x인 랜선은 i=1KAi/x개 만들 수 있습니다.

    이 값이 N 이상이라면 더 긴 길이를 시도해보고, 그렇지 않다면 더 짧은 길이를 시도해보면 됩니다.

    int arr[10020];
    
    void Main(){
    	int n, k; cin >> n >> k;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	ll st = 1, ed = 1e10, ans = 0;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll res = 0;
    		for (int i = 1; i <= n; i++){ res += arr[i] / mid; }
    		if (res >= k){ st = mid+1; ans = mid; } else{ ed = mid-1; }
    	}
    	cout << ans;
    }

    3. [12015] 가장 긴 증가하는 부분 수열 2 (Gold II)

    더보기

    A. [11053] 가장 긴 증가하는 부분 수열 의 점화식을 그대로 가지고 올 수는 없는 문제입니다.

     

    그러니, DP를 참신하게 세워봅시다.

    DPi:= 길이가 i인 LIS의 마지막 원소 중 최솟값

    도대체 이걸로 뭘 하나 싶겠지만, 이러면 DPi<DPi+1이라는 재밌는 사실이 하나 나오게 됩니다.

     

    그리고 저 사실이 큰 역할을 해줍니다.

    DPk<Ai인 가장 큰 k를 찾았다면, 이는 길이 k+1의 LIS를 만들 수 있다는 것이며, 더 나아가 길이 k+2의 LIS는 만들 수 없다는 소리가 되기 때문입니다.

    그러니, DPk<Ai인 가장 큰 k를 찾은 뒤, DPk+1을 이에 맞게 업데이트해주면 됩니다.

    만약 길이 k+1의 LIS가 없었다면, 답의 길이도 1 증가시켜야 합니다.

     

    실제 구현에서는 구현의 편의를 위해 DPkAi인 가장 작은 k를 찾은 뒤 DPk를 업데이트해줍니다. 이게 위 방식과 동일함은 DPk1<AiDPk를 생각해보면 자명합니다.

    int arr[1000020], lis[1000020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	int cnt = 0;
    	for (int i = 1; i <= n; i++){
    		auto idx = lower_bound(lis, lis+cnt, arr[i]) - lis;
    		if (idx == cnt){ cnt += 1; } lis[idx] = arr[i];
    	}
    	cout << cnt;
    }

    4. [10816] 숫자 카드 2 (Silver IV)

    더보기

    "1. [1920] 수 찾기" 문제의 강화판입니다.

    이제는 개수를 세야 하는데, 개수는 upper_bound의 위치 - lower_bound의 위치 로 구할 수 있습니다.

     

    + equal_range는 lower_bound와 upper_bound의 위치를 동시에 return합니다.

    이름이 equal_range인 이유는, [lower, upper) 구간에 target과 동일한 값이 들어있기 때문입니다.

    int arr[500020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	sort(arr+1, arr+n+1);
    	int q; cin >> q; while (q--){
    		int x; cin >> x;
    		auto p = equal_range(arr+1, arr+n+1, x);
    		cout << p.sc - p.fr << ' ';
    	}
    }
    더보기

    "1. [1920] 수 찾기"에서 set을 박은 걸 기억하나요?

    여기서는 { x: count }의 형태로 map을 박으면 됩니다.

    map<int, int> mp;
    
    void Main(){
    	int n; cin >> n;
    	while (n--){ int x; cin >> x; mp[x] += 1; }
    	int q; cin >> q;
    	while (q--){ int x; cin >> x; cout << mp[x] << ' '; }
    }

    5. [3020] 개똥벌레 (Gold V)

    더보기

    밑에서 올라오는 장애물과 위에서 내려오는 장애물을 따로 처리해봅시다.

    높이 h에서 날아갈 때 부딪히는 장애물은 밑에서 올라오는 장애물 중 h 위로 올라가는 장애물의 개수 + 위에서 내려오는 장애물 중 h 밑으로 내려가는 장애물의 개수 가 됩니다.

    이 2개 모두 이진 탐색을 사용하여 구해줄 수 있습니다.

     

    이를 모든 높이에서 돌려준 뒤, 파괴해야 하는 장애물의 최솟값과 위치를 출력해주면 됩니다.

     

    시간복잡도는 O(N+HlogN)입니다.

    vector<int> v1, v2;
    
    void Main(){
    	int n, m; cin >> m >> n;
    	for (int i = 1; i <= m; i++){
    		int x; cin >> x;
    		if (i&1){ v1.push_back(x); }
    		else{ v2.push_back(n-x+1); }
    	}
    	sort(all(v1)); sort(all(v2));
    	int ans = 1e9, cnt = 0;
    	for (int i = 1; i <= n; i++){
    		int c1 = v1.end() - lower_bound(all(v1), i);
    		int c2 = upper_bound(all(v2), i) - v2.begin();
    		int res = c1+c2;
    		if (ans > res){ ans = res; cnt = 0; }
    		if (ans == res){ cnt += 1; }
    	}
    	cout << ans << ' ' << cnt << endl;
    }
    더보기

    (풀이 1에서 이어집니다.)

    그런데 굳이 이진 탐색을 써야 할까요?

    누적합을 사용해서 위에서 나온 2개의 경우를 계산해둘 수도 있다.

    이제 시간복잡도는 O(N+H)가 되었습니다.

    int c1[500020], c2[500020];
    
    void Main(){
    	int n, m; cin >> m >> n;
    	for (int i = 1; i <= m; i++){
    		int x; cin >> x;
    		if (i&1){ c1[x] += 1; }
    		else{ c2[n-x+1] += 1; }
    	}
    	for (int i = n; i >= 1; i--){ c1[i] += c1[i+1]; }
    	for (int i = 1; i <= n; i++){ c2[i] += c2[i-1]; }
    	int ans = 1e9, cnt = 0;
    	for (int i = 1; i <= n; i++){
    		int res = c1[i] + c2[i];
    		if (ans > res){ ans = res; cnt = 0; }
    		if (ans == res){ cnt += 1; }
    	}
    	cout << ans << ' ' << cnt << endl;
    }
    더보기

    사실 누적합을 이렇게 써서 풀 수도 있습니다.

    밑에서 올라오는 건 [1, h] 구간에 장애물이 생기는 거고, 위에서 내려오는 건 [h, H] 구간에 장애물이 생기는 것이 됩니다.

    그리고 [st, ed] 구간의 장애물은 Ast에 1을 더한 뒤 Aed+1에 1을 빼고, 나중에 누적합을 넣는 방식으로 구현할 수 있습니다.

    시간복잡도는 O(N+H)가 됩니다.

    int prf[500020];
    
    void Main(){
    	int n, m; cin >> n >> m;
    	for (int i = 1; i <= n; i++){
    		int pos; cin >> pos;
    		if (i&1){ prf[1] += 1; prf[pos+1] -= 1; }
    		else{ prf[m-pos+1] += 1; prf[m+1] -= 1; }
    	}
    	for (int i = 1; i <= m; i++){ prf[i] += prf[i-1]; }
    	int mn = 1e9, mnc = 0;
    	for (int i = 1; i <= m; i++){
    		if (mn > prf[i]){ mn = prf[i]; mnc = 0; }
    		if (mn == prf[i]){ mnc += 1; }
    	}
    	cout << mn << ' ' << mnc;
    }

    6. [2792] 보석 상자 (Silver II)

    더보기

    질투심이 낮아질수록 보석을 받는 학생 수가 늘어나므로, 질투심을 기준으로 Parametric Search를 돌려봅시다.

     

    질투심이 X 이하가 되도록 A개의 보석을 나누면, 최소 A/X명의 학생에게 보석을 나눠줘야 합니다.

    그러니 이를 모든 종류의 보석에 돌려줬을 때의 결과 i=1NAi/X를 계산한 뒤, 이게 N 이하라면 질투심을 더 낮춰보고, 아니면 질투심을 더 높여주면 됩니다.

     

    시간복잡도는 O(Nlog2109)가 됩니다.

    ll arr[300020];
    
    void Main(){
    	ll n, m; cin >> m >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	ll st = 1, ed = 2e9, ans = 0;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll res = 0;
    		for (int i = 1; i <= n; i++){ res += (arr[i]+mid-1)/mid; }
    		if (res <= m){ ed = mid-1; ans = mid; } else{ st = mid+1; }
    	}
    	cout << ans;
    }

    7. [2512] 예산 (Silver III)

    더보기

    상한을 X라고 합시다. 자명하게, 상한을 낮게 잡을 수록 배정 금액이 낮아집니다.

    그러니 파라메트릭 서치로 잘 풀어주면 될 것 같습니다.

     

    상한이 X일 때 배정되는 예산은 다음과 같이 구할 수 있습니다. i=1Nmin(Ai,X)

    이 예산이 총 예산을 넘는다면 상한을 작게, 아니라면 상한을 크게 잡아주면 됩니다.

    ll arr[10020];
    
    void Main(){
    	int n; cin >> n; ll mx = 0;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; mx = max(mx, arr[i]); }
    	ll m; cin >> m;
    	ll st = 1, ed = mx, ans = 0;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll res = 0;
    		for (int i = 1; i <= n; i++){ res += min(arr[i], mid); }
    		if (res <= m){ st = mid+1; ans = mid; } else{ ed = mid-1; }
    	}
    	cout << ans;
    }

    8. [1072] 게임 (Silver III)

    더보기

    승률이 한 번 바뀐 뒤로는 더 오르거나 / 가만히 있기 때문에 안 바뀐 상태로 돌아가는 경우는 없습니다.

    그러니 파라메트릭 서치를 사용해서 추가 플레이 횟수를 구해봅시다.

     

    P회 게임을 더 한 뒤의 승률은 100X+PY+P%가 됩니다. 그러니 이 값이 원래 있던 Z와 다른지 판별해주면 됩니다.

    [1,1012] 구간에서 돌려주면 답이 잘 나옵니다.

    아무리 돌려도 Z가 변하지 않는다면 -1을 출력해야 함에 주의하세요.

    void Main(){
    	ll x, y; cin >> x >> y;
    	ll z = 100*y / x;
    	ll st = 1, ed = 1e12, ans = -1;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll res = 100 * (y+mid)/(x+mid);
    		if (z != res){ ed = mid-1; ans = mid; } else{ st = mid+1; }
    	}
    	cout << ans;
    }

    9. [2805] 나무 자르기 (Silver III)

    더보기

    높이가 낮아질수록 가져갈 수 있는 나무의 길이는 길어집니다.

    그러니 최적의 높이를 Parametric Search로 찾아봅시다.

     

    높이를 X미터로 설정했을 때 가져갈 수 있는 나무의 길이는 i=1nmax(Ai,0)입니다.

    그러니, 저 길이가 목표인 M보다 크거나 같다면 더 높은 높이를, 아니면 더 낮은 높이를 시도해보면 됩니다.

     

    시간복잡도는 O(Nlog109)입니다.

    ll arr[1000020];
    
    void Main(){
    	int n; ll m; cin >> n >> m;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	ll st = 0, ed = 1e9, ans = 0;
    	while (st <= ed){
    		ll mid = st+ed >> 1;
    		ll res = 0;
    		for (int i = 1; i <= n; i++){ res += max(arr[i]-mid, 0LL); }
    		if (res >= m){ st = mid+1; ans = mid; } else{ ed = mid-1; }
    	}
    	cout << ans;
    }

    10. [2613] 숫자구슬 (Gold II)

    더보기

    그룹의 합의 최댓값이 클 수록 그룹의 개수는 작아질테니, 그룹의 합의 상한선을 기준으로 Parametric Search를 돌려봅시다.

     

    만약 그룹의 합이 X를 넘지 못한다면, 어떻게 합쳐야 할까요?

    다양한 방법이 있겠지만, 1번 그룹만을 보면 '되는대로 합친다'가 답이 됩니다.

    그럼 이를 토대로 2번 그룹을 보면 역시 '되는대로 합치는' 게 최적이고,

    이런 식으로 모든 그룹에 대해 "합이 넘지 않는 동안 계속 합친다"를 해주는 게 최적입니다.

     

    이런 식으로 그룹을 합쳤을 때 M개 이하의 그룹이 만들어졌다면 더 작은 최댓값을 탐색하고, 아니면 더 큰 최댓값을 탐색해보면 됩니다.

     

    주의할 점으로는, 그룹의 개수가 정확히 M개여야 하지만, 위 방식으로는 정확히 M개가 아닌 경우가 만들어질 수도 있습니다.

    이 경우, 그냥 M개가 될 때까지 마음대로 더 잘라주면 됩니다.

     

    + 마음대로 더 자르다가 최댓값이 더 작아지면 어떡해야 하죠?

    만약 최댓값이 더 작아질 수 있다면 그걸 Parametric Search에서 잡아냈을 테니, 그런 경우는 없습니다.

    int arr[320];
    
    void Main(){
    	int n, m; cin >> n >> m; int mx = 0;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; mx = max(mx, arr[i]); }
    	int st = mx, ed = 30000, ans = 0;
    	while (st <= ed){
    		int mid = st+ed >> 1;
    		int sum = 0, res = 1;
    		for (int i = 1; i <= n; i++){
    			sum += arr[i];
    			if (sum > mid){ sum = arr[i]; res += 1; }
    		}
    		if (res <= m){ ed = mid-1; ans = mid; } else{ st = mid+1; }
    	}
    	vector<int> anv;
    	int sum = 0, ptr = 1;
    	for (int i = 1; i <= n; i++){
    		sum += arr[i];
    		if (sum > ans){ anv.push_back(i-ptr); sum = arr[i]; ptr = i; }
    	}
    	anv.push_back(n+1-ptr);
    	cout << ans << endl;
    	int vl = anv.size();
    	for (int& x : anv){
    		while (vl < m){
    			if (x == 1){ break; }
    			cout << 1 << ' ';
    			x -= 1; vl += 1;
    		}
    		cout << x << ' ';
    	}
    }

    11. [3745] 오름세 (Gold III)

    더보기

    오름세의 정의를 생각해보면, Increasing Sequence와 동일함을 알 수 있습니다.

    그 중 가장 긴 (Longest) 걸 찾아야 하니, 정직한 LIS 문제임을 알 수 있습니다.

    int arr[100020], lis[100020];
    
    void Main(){
    	int n; while (cin >> n){
    		for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    		int cnt = 0;
    		for (int i = 1; i <= n; i++){
    			auto idx = lower_bound(lis, lis+cnt, arr[i]) - lis;
    			if (idx == cnt){ cnt += 1; } lis[idx] = arr[i];
    		}
    		cout << cnt << endl;
    		for (int i = 0; i < cnt; i++){ lis[i] = 0; }
    	}
    }

    12. [2352] 반도체 설계 (Gold III)

    더보기

    꼬이지 않도록 연결한다는 건, i<j일 때 Ai<Aj라는 것입니다.

    그러니, 이 개수가 최대라는 건 i1<i2<<ik일 때 Ai1<Ai2<<Aikk를 최대화해야 한다는 것입니다.

     

    뭔가 익숙하지 않나요? 방금 설명한 문제는 LIS 문제와 동일합니다.

    그러니, 꼬이지 않는 연결선의 개수 = LIS의 길이가 됩니다.

     

    N40000이니 O(NlogN)으로 구해줘야 합니다.

    int arr[40020], lis[40020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	int cnt = 0;
    	for (int i = 1; i <= n; i++){
    		auto idx = lower_bound(lis, lis+cnt, arr[i]) - lis;
    		if (idx == cnt){ cnt += 1; } lis[idx] = arr[i];
    	}
    	cout << cnt;
    }

    13. [1365] 꼬인 전깃줄 (Gold III)

    더보기

    자르는 전선의 개수를 최소화하는 것과 꼬이지 않는 전깃줄의 개수를 최대화하는 건 동일하므로, 꼬이지 않는 전깃줄의 개수를 세봅시다.

     

    그런데 꼬이지 않는 전깃줄의 개수는 12. [2352] 반도체 설계 문제에서 이미 구해봤습니다.

    즉, LIS의 길이가 됩니다.

     

    이번에는 자르는 전선의 개수이니, N - (LIS의 길이)를 출력해주면 됩니다.

    N100000이니, O(NlogN)으로 풀어주면 됩니다.

    int arr[100020], lis[100020];
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	int cnt = 0;
    	for (int i = 1; i <= n; i++){
    		auto idx = lower_bound(lis, lis+cnt, arr[i]) - lis;
    		if (idx == cnt){ cnt += 1; } lis[idx] = arr[i];
    	}
    	cout << n-cnt;
    }

    14. [10025] 게으른 백곰 (Silver IV)

    더보기

    [xK,x+K] 구간에서 [x+1K,x+1+K] 구간으로 이동할 때는 Ax+1+K를 더한 뒤 AxK를 빼주면 됩니다.

    그러니 우선 [2K,0] 구간에서의 얼음 개수를 구한 뒤, 이를 오른쪽으로 1칸씩 밀어주면서 최댓값을 구해주면 됩니다.

     

    시간복잡도는 O(X=106)입니다.

    ll arr[1000020];
    
    void Main(){
    	int n, k;
    	cin >> n >> k;
    	for (int i = 1; i <= n; i++){
    		int x, y;
    		cin >> x >> y;
    		arr[y] += x;
    	}
    	ll ans = 0, res = 0;
    	for (int i = 0; i <= 1000000; i++){
    		int j = i - 2*k - 1;
    		if (j >= 0) res -= arr[j];
    		res += arr[i];
    		ans = max(ans, res);
    	}
    	cout << ans;
    }

    15. [2467] 용액 (Gold V)

    더보기

    입력으로 들어오는 A는 오름차순으로 정렬되어있습니다.

    그러니 바로 두 포인터를 p1 = 1에, p2 = N에 놓고 시작해봅시다.

     

    만약 Ap1Ap2의 부호가 같다면, |Ap1+Ap1+1||Ap21+Ap2| 중 더 작은 걸 찾고 끝내면 됩니다.

    그렇지 않다면, Ap10Ap2입니다.

    만약 |Ap1|<|Ap2|라면 p2를 움직여주면 되고 (p2 -= 1),

    그렇지 않다면 p1을 움직여주면 됩니다 (p1 += 1).

     

    답은 두 포인터를 이동하면서 만난 모든 |Ap1+Ap2| 중 최솟값이고, 이 때의 Ap1,Ap2를 출력해주면 됩니다.

     

    시간복잡도는 O(N)입니다.

    ll arr[100020];
    
    ll ans = 1e15, x=0, y=0;
    void f(int p1, int p2){
    	ll res = abs(arr[p1] + arr[p2]);
    	if (ans > res){ ans = res; x = arr[p1]; y = arr[p2]; }
    }
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	int p1 = 1, p2 = n;
    	while (p1 < p2){
    		f(p1, p2);
    		if (arr[p1] > 0){ f(p1, p1+1); break; }
    		if (arr[p2] < 0){ f(p2-1, p2); break; }
    		if (abs(arr[p1]) <= abs(arr[p2])){ p2 -= 1; }
    		else{ p1 += 1; }
    	}
    	cout << x << ' ' << y << endl;
    }
    더보기

    사실 이 문제는 이진 탐색만으로도 풀 수 있습니다.

     

    1번 용액 x를 고정한다면, 2번 용액은 x에 가장 가까운 값으로 선택해야 하므로,

    x를 기준으로 이진 탐색을 해줘서 x 미만의 수 중 가장 큰 수와 x 이상의 수 중 가장 작은 수 2개를 찾을 수 있고, 둘 중 더 좋은 해를 선택해주면 됩니다.

     

    시간복잡도는 O(NlogN)입니다.

    ll arr[100020];
    
    ll ans = 1e15, x=0, y=0;
    void f(int p1, int p2){
    	ll res = abs(arr[p1] + arr[p2]);
    	if (ans > res){ ans = res; x = arr[p1]; y = arr[p2]; }
    }
    
    void Main(){
    	int n; cin >> n;
    	for (int i = 1; i <= n; i++){ cin >> arr[i]; }
    	for (int i = 1; i <= n; i++){
    		int j1 = lower_bound(arr+1, arr+n+1, -arr[i]) - arr;
    		int j2 = j1 - 1;
    		if (j1 == i){ j1 += 1; } if (j2 == i){ j2 -= 1; }
    		if (j1 <= n){ f(i, j1); }
    		if (1 <= j2){ f(i, j2); }
    	}
    	if (x > y){ swap(x, y); }
    	cout << x << ' ' << y;
    }
Designed by Tistory.