풀이

1. 인쇄 대기목록의 가장 앞에 있는 문서 J를 대기 목록에서 꺼낸다.
2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 있다면 J를 대기 목록에서 가장 마지막으로 넣는다.
3. 그렇지 않으면 J문서를 인쇄한다.

앞에서 빼고, 뒤에서 다시 넣고 하는 자료구조는 Deque가 있다.

현재 가장 앞에 있는 문서의 중요도가 뒤에 있는 모든 중요도보다 높거나 같다면 그 문서를 출력하고,

아니라면 현재 가장 앞에 있는 문서를 뽑아서 뒤에 다시 넣는다.

 

뒤에 있는 모든 중요도 중에서 가장 높은 중요도는 어떻게 뽑을까?

간단하게 for문을 돌려 뽑을 수도 있지만 *max_element를 이용하면 쉽게 뽑아올 수 있다.

다만 시간 복잡도는 for문과 동일하지만, 간단하게 코드를 짤 수 있어서 편하고 용이하다.

#include <string>
#include <vector>
#include <deque>
#include <algorithm>
using namespace std;

int solution(vector<int> priorities, int location) {
    int answer = 0;
    deque<pair<int, int> > dq;
    for (int i = 0; i < priorities.size(); i++) {
        dq.push_back({ priorities[i], i });
    }

    while (!dq.empty()) {
        int current_priority = dq.front().first;
        int current_location = dq.front().second;
        dq.pop_front();
        int max_priority = (*max_element(dq.begin(), dq.end())).first;
        if (current_priority >= max_priority) {
            answer++;
            if (current_location == location) return answer;
        }
        else {
            dq.push_back({ current_priority, current_location });
        }
    }

    return answer;
}

파라메트릭 서치

생각보다 받아들이는데 어려운 개념이다.

이 알고리즘은 최적화 문제결정 문제로 바꾸는 기법이다.

조건을 만족하면서도 최대한 많이 가져갈 수 있는 방법을 찾는 느낌?? 혹은

조건을 만족하면서도 최대한 적게 가져갈 수 있는 방법을 찾는 느낌이라고 생각하면 좋다.

 

예시를 들어보면 아래의 문제를 인용해서 생각해보자.

BOJ2110 공유기 설치

대표적인 파라메트릭 서치 문제다.

공유기 C개를 설치해야 하는데 두 공유기 사이의 거리를 최대한으로 하려면 어느 정도쯤에 설치해야 할까?라는 문제다.

 

(아래의 예시는 위의 문제 스포가 될 수 있어서 임의로 그냥 생각한 풀이입니다.)

예를 들어보자.

공유기 3개를 설치해야 하는데

만약에 내가 두 공유기 사이의 거리를 2로 설치한다고 가정했을 때,

2씩 공유기를 설치해보니 공유기 3개가 모두 설치되었다. 이는 조건을 만족하니까 우리가 찾는 정답이므로 2로 갱신한다.

 

그렇다면, 여기서 끝일까? 아니다.

한 발자국 더 나아가 보면서 생각해 봐야 한다.

두 공유기 사이의 거리를 3으로 확장해보면 어떨까?

3씩 공유기를 설치해보니 공유기 3개가 모두 설치되었다. 분명 2일 때도 공유기 3개가 모두 설치되었는데? 조금 더 멀리 설치해도 공유기 3개가 모두 설치된 것이다. 마찬가지로 조건을 만족하니까 기존에 저장했던 2를 3으로 갱신한다.

 

그렇다면, 진짜로 끝일까? 아니다.

한 발자국 더 나아가 보자.

두 공유기 사이의 거리를 4로 확장해보면?

4씩 공유기를 설치해보니 공유기 2개가 설치되고 1개는 설치되지 못했다.

이렇게 된다면 주어진 조건 공유기 3개를 설치하지 못했으므로 정답이 아니다.

따라서 여기서 탐색을 멈춘다. 즉, 정답에 저장된 3이 답이 된다.

 

이렇듯, 범위를 좁혀가면서 주어진 조건을 만족하면 한 걸음 더 나아가 보고 아니라면 돌아오는 느낌으로 탐색을 하면 된다.

 

이런 기법은 많은 문제를 풀면 느낌이 오게 된다.

아래의 문제들은 백준 사이트에서 파라메트릭 서치 대표 문제들이다.

시간 나면 다시 풀어보자.

랜선 자르기

기타 레슨

공유기 설치

나무 자르기

 

기본적인 구현은 다음과 같이 생각하면 좋다.

1. 초깃값 세팅 start, end

2. mid가 결정된 후 그 mid가 조건에 맞는지 검사하기

3. 조건에 따라 범위 수정 후에 다시 2번으로 돌아가기

int start = 0; // 문제마다 start와 end 값은 변동될 수 있다.
int end = 1e8; // 범위가 커진다면 long long 자료형으로 오버플로우를 방지할 수 있다.
int ans = 0;
while(start <= end)
{
    int mid = (start + end) / 2;
    if(calc(mid)) // 조건을 만족한다면
    {
    	ans = mid;
        s = mid + 1; // 한 걸음 더 가보자.
    }
    else
    	e = mid - 1; // 조건을 만족하지 못하니까, 탐색 범위를 줄여보자.
}

또, 파라메트릭 서치 같은 문제들은 범위가 어마어마하게 크다. 보통 O(N)으로 해결될 수 없는 문제들 같은 경우 이러한 방법도 생각해보는 것도 좋다.

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
[cmath] math 관련 함수들  (0) 2021.05.07
[GCD, LCM] 유클리드 호제법  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

cmath

cmath에는 c++에서 각종 수학 함수들을 가지고 있다.

신기하게도 min, max는 algorithm에 구현되어있다는 것을 명심하자.

제곱 구하기

#include <cmath>

// C++ (함수 오버로딩 = 함수 중복 정의)
double pow(double x, double y);
float pow(float x, float y);
float pow(float x, int y);
long double pow(long double x, long double y);
long double pow(long double x, int y);

C++에서는 함수의 이름을 pow로 통일해서 사용하면 된다.

 

제곱근 구하기

#include <cmath>

double sqrt(double x);
float sqrt(float x);
long double sqrt(long double x);

 

올림

#include <cmath>

double ceil(double x);
float ceil(float x);
long double ceil(long double x);

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
파라메트릭서치 (Parametric Search)  (0) 2021.05.09
[GCD, LCM] 유클리드 호제법  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

유클리드 호제법?

두 수의 최대 공약수를 O(lgN)만에 구하는 방법이다.

생각보다 자주 까먹으니까.. 틈틈이 시간 나면 한 번씩 봐주자.

재귀 함수 구현

int gcd(int a, int b)
{
    if(b == 0) return a;
    else return gcd(b, a % b);
}

반복문 구현

전제 조건 : a > b
while(1)
{
    int r = a % b;
    if(r == 0) return b;
    
    a = b;
    b = r;
}

최소 공배수는?

int lcm(int a, int b)
{
    return (a / gcd(a, b)) * (b / gcd(a, b)) * gcd(a, b);
}

최소 공배수에서 gcd(a, b)를 3번이나 해주는 이유는 특정 구간에서 오버플로우의 방지를 막기 위해서이다.

'Algorithm > Algorithm Concept' 카테고리의 다른 글

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
파라메트릭서치 (Parametric Search)  (0) 2021.05.09
[cmath] math 관련 함수들  (0) 2021.05.07
stable sort과 unstable sort  (0) 2021.04.26

stoi

c++의 매력을 느낄 수 있는 함수입니다.

바로 문자열을 정수로 바꿔주는 엄청난..

#include <string>

int a = stoi("1234"); // 1234
int b = stoi("-1234"); // -1234
int c = stoi("+1234"); // 1234

신기한 점은 문자열 앞에 '+''-' 부호가 있어도 그것을 반영해서 나타내 준다는 점!!

'CS > Data Structure' 카테고리의 다른 글

[C++] priority_queue  (0) 2021.08.04
[C++] multimap  (0) 2021.04.28
[C++] map  (0) 2021.04.28
[C++] unordered_set  (0) 2021.04.12
[C++] multiset  (0) 2021.04.12

풀이

처음에는 이 문제를 보고 어떤 자료구조를 사용해야 빠르게 해결할 수 있을까?를 많이 고민한 문제.

문제에서 2, 5, 8, 0을 누를 때는 왼쪽 엄지랑 오른쪽 엄지중 더 가까운 곳에 있는 엄지가 먼저 누르기 때문에

좌표를 사용해야겠다는 생각이 떠올랐다.

 

좌표 (행,열) 기준!

1번 키패드의 좌표는 (0, 0)

2번 키패드의 좌표는 (0, 1)

0번 키패드의 좌표는 (3, 1)

~번 키패드의 좌표를 표시해주는 자료구조 중 map을 사용하면 편할 거 같아서 map을 채택하면 쉽게 구현할 수 있다.

이렇게 각 키패드 번호마다 좌표를 부여해서, 해당 키패드를 누를 때 왼쪽 엄지랑 오른쪽 엄지의 거리를 구해서 더 작은 거리를 가진 엄지가 먼저 누르도록 구현하면 된다.

#include <string>
#include <vector>
#include <cmath>
#include <map>
#include <iostream>
using namespace std;

map<char, pair<int,int>> m;

void init() {
    m['1'] = pair<int,int>(0,0);
    m['2'] = pair<int,int>(0,1);
    m['3'] = pair<int,int>(0,2);
    m['4'] = pair<int,int>(1,0);
    m['5'] = pair<int,int>(1,1);
    m['6'] = pair<int,int>(1,2);
    m['7'] = pair<int,int>(2,0);
    m['8'] = pair<int,int>(2,1);
    m['9'] = pair<int,int>(2,2);
    m['*'] = pair<int,int>(3,0);
    m['0'] = pair<int,int>(3,1);
    m['#'] = pair<int,int>(3,2);
}

string solution(vector<int> numbers, string hand) {
    string answer = "";
    
    char left = '*';
    char right = '#';
    init();
    
    for(int i = 0; i < numbers.size(); i++) {
        int n = numbers[i];
        
        if(n == 1 || n == 4 || n == 7) answer += "L";
        else if(n == 3 || n == 6 || n == 9) answer += "R";
        else {
            int left_distance = abs(m[left].first - m[n + '0'].first) + abs(m[left].second - m[n + '0'].second);
            int right_distance = abs(m[right].first - m[n + '0'].first) + abs(m[right].second - m[n + '0'].second);
            if(left_distance == right_distance) {
                if(hand == "left")
                    answer += "L";
                else
                    answer += "R";
            }
            else if(left_distance < right_distance) answer += "L";
            else answer += "R";
        }
        
        if(answer.back() == 'R')
            right = n + '0';
        else
            left = n + '0';
    }
    
    return answer;
}

풀이

문제 요약.

1. 단 한 명의 선수를 제외하고 나머지 모든 선수들은 완주를 했다.

2. 선수들의 수는 10만

 

간단하게 생각하면 참가자와 완주자를 2중 포문으로 모두 대조해서 찾을 수 있지만

10만 X 10만은 시간 초과가 발생할 수 있다.

따라서 더 간단하게 대조하는 방법은 map 자료구조를 사용하는 것이다.

map의 특징 중 하나인 검색의 속도가 O(lgN)을 보장하기 때문에 N이 10만이 들어온다 하더라도

무리 없이 해결할 수 있다.

 

우선 참가자들을 순회하면서 해당 선수 이름의 횟수를 늘려준다.

그리고 완주자들을 순회하면서 해당 선수 이름의 횟수를 빼준다.

만약에 해당 선수의 이름을 조회하였을 때 그 선수의 값이 0이 아닌 다른 숫자라면?

그 선수는 완주하지 못한 이름이다. 따라서 그 이름을 리턴하면 끝.

#include <string>
#include <vector>
#include <map>
using namespace std;

string solution(vector<string> participant, vector<string> completion) {
    string answer = "";
    
    map<string, int> m;
    for(int i = 0; i < participant.size(); i++) {
        string name = participant[i];
        m[name]++;
    }
    
    for(int i = 0; i < completion.size(); i++) {
        string name = completion[i];
        m[name]--;
    }
    
    for(auto p : m) {
        if(p.second > 0) {
            answer = p.first;
            break;
        }
    }
    
    return answer;
}

풀이

크레인 인형 뽑기를 할 때, 제일 위에 쌓여있는 인형부터 뽑아지므로 '스택' 자료구조를 이용하면 쉽게 해결할 수 있다.

문제에서 주어진 board를 각각의 stack에 넣는다. 주의할 점은 0일 때는 인형이 존재하지 않는 것이므로 0이 아닌 값만 스택에 push 해야 한다.

 

예를 들어 문제에 나온 예시를 기준으로 stack에 쌓이는 결과를 보면

3 4      
5 2 2    
1 4 5 1  
3 4      
1 2 1 3  

주어진 board를 stack에 넣고 나서 moves 배열을 하나씩 돌면서 인형 뽑기를 시작한다.

이때 moves 배열로 뽑힌 것을 담는 바구니 역시 stack으로 선언한다.

 

이제 문제에서 요구하는 상황을 위와 맞물려서 생각해보자.

1. 만약 같은 모양의 인형 두 개가 바구니에 연속해서 쌓이게 되면 두 인형은 터뜨려지면서 사라진다.

현재 바구니에 담긴 스택의 가장 상단 인형과 뽑기로 뽑은 인형이 같다면 터지면서 정답에 +2를 추가하면 된다.

 

2. 격자의 가장 아래 칸부터 차곡차곡 쌓여있다.

위에서 각 board의 입력을 stack에 쌓았다.

 

3. 만약 인형이 없다면 아무 일도 일어나지 않는다.

크레인으로 인형을 뽑았는데 만약에 빈 stack 이면 비교할 필요가 없다는 것이다. 인형이 없으니까.

 

 

#include <string>
#include <vector>
#include <stack>
#include <iostream>
using namespace std;

stack<int> st[31];
stack<int> ret;

int solution(vector<vector<int>> board, vector<int> moves) {
    int answer = 0;
    int len = board.size();
    // 사전 작업
    for(int i = len - 1; i >= 0; i--) {
        for(int j = 0; j < len; j++) {
            if(board[i][j] != 0)
                st[j + 1].push(board[i][j]);
        }
    }
    
    // 크레인 인형 뽑기 시작
    for(int i = 0; i < moves.size(); i++) {
        int idx = moves[i];
        if(!st[idx].empty()) {
            if(!ret.empty() && ret.top() == st[idx].top()) {
                ret.pop();
                answer += 2;
            }
            else {
                ret.push(st[idx].top());
            }
            st[idx].pop();
        }
    }
    return answer;
}

풀이

읽다 보니까 운영체제 프로세스 스케줄링이 생각나는 문제.

모든 소가 농장에 최소한의 시간으로 들어오려면 어떻게 해야 할까?

빨리 도착한 소부터 넣어주면 된다.

빨리 도착한 소부터 차례대로 하려면 도착시간을 기준으로 오름차순 정렬해주자.

 

처음 도착한 소는 도착 시간과 검문 시간을 모두 더해 정답에 넣어준다.

이유는 한 마리의 소만 존재한다면 그 소가 도착하고 들어간 시간이 정답이기 때문이다.

 

두 번째 소부터는 다음과 같은 규칙대로 시간을 계산한다.

이전에 들어온 소가 농장에 입장하기까지 걸린 시간을 ans라고 하자.

1. 두 번째 소가 ans보다 더 늦게 도착했다면, 즉 기다린 시간이 없다면

ans = 두 번째 소 도착 시간 + 검문 시간

2. 두 번째 소가 ans보다 빠르게 도착했거나 같다면 이 때는 기다린 시간이 존재한다.

따라서 ans = ans(이전 소의 입장 시간) + 두 번째 소의 검문 시간

 

이렇게 소를 하나씩 체크하면서 ans를 갱신해나가면 된다.

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

int n;
vector<pair<int, int>> v;
int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);

	cin >> n;
	v.reserve(n);
	for (int i = 0; i < n; i++)
	{
		int a, b;
		cin >> a >> b;
		v.push_back({ a,b });
	}
	sort(v.begin(), v.end());
	int ans = v[0].first + v[0].second;
	for (int i = 1; i < v.size(); i++)
	{
		// 기다린 시간이 없으면 도착시간 + 검문시간
		if (ans <= v[i].first)
		{
			ans = v[i].first + v[i].second;
		}
		// 기다린 시간이 있다면 저장된 시간 + 검문시간
		else
		{
			ans = ans + v[i].second;
		}
	}

	cout << ans << '\n';
	return 0;
}

 

multimap?

multimap은 기본적으로 map과 비슷하지만 다른 부분들이 있다.

비슷한 부분은 [map]에서 확인하자.

1. Key의 중복을 허용한다.

2 [] 연산자를 지원하지 않기 때문에 insert() 멤버 함수로 삽입해야 한다.

 

연습해보기

#include <iostream>
#include <map>
using namespace std;

int main()
{
	// less <
	// left child key < parent key <= right child key
	multimap<int, int> mm;

	//multimap은 [] operator를 제공하지 않는다.
	//mm[5] = 100;
	mm.insert(pair<int, int>(5, 100));
	mm.insert(pair<int, int>(3, 100));
	mm.insert(pair<int, int>(8, 30));
	mm.insert(pair<int, int>(3, 40));
	mm.insert(pair<int, int>(1, 70));
	mm.insert(pair<int, int>(7, 100));
	mm.insert(pair<int, int>(8, 50));

	for (auto iter = mm.begin(); iter != mm.end(); iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';

	cout << "mm.count(3): " << mm.count(3) << '\n';
	cout << "mm.count(9): " << mm.count(9) << '\n';
	cout << "mm.count(5): " << mm.count(5) << '\n';

	auto iter = mm.find(3);
	if (iter != mm.end())
		cout << "첫 번째 key 3에 매핑된 value: " << iter->second << '\n';
	else
		cout << "key 3이 multimap에 없습니다.\n";
	return 0;
}
(1, 70) (3, 100) (3, 40) (5, 100) (7, 100) (8, 30) (8, 50)
mm.count(3) : 2
mm.count(9) : 0
mm.count(5) : 1
첫 번째 key 3에 매핑된 value : 100
#include <iostream>
#include <map>
using namespace std;

int main()
{
	multimap<int, int> mm;

	mm.insert(pair<int, int>(5, 100));
	mm.insert(pair<int, int>(3, 100));
	mm.insert(pair<int, int>(8, 30));
	mm.insert(pair<int, int>(3, 40));
	mm.insert(pair<int, int>(1, 70));
	mm.insert(pair<int, int>(7, 100));
	mm.insert(pair<int, int>(8, 50));

	for (auto iter = mm.begin(); iter != mm.end(); iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';

	multimap<int, int>::iterator lower_iter = mm.lower_bound(3);
	multimap<int, int>::iterator upper_iter = mm.upper_bound(3);

	cout << "[lower_iter, upper_iter): ";
	for (auto iter = lower_iter; iter != upper_iter; iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';

	auto equal_iter = mm.equal_range(3);
	cout << "[equal_iter.first, equal_iter.second): ";
	for (auto iter = equal_iter.first; iter != equal_iter.second; iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';
	return 0;
}
(1, 70) (3, 100) (3, 40) (5, 100) (7, 100) (8, 30) (8, 50)
[lower_iter, upper_iter): (3, 100) (3, 40)
[equal_iter.first, equal_iter.second): (3, 100) (3, 40)

'CS > Data Structure' 카테고리의 다른 글

[C++] priority_queue  (0) 2021.08.04
[C++] stoi  (0) 2021.05.06
[C++] map  (0) 2021.04.28
[C++] unordered_set  (0) 2021.04.12
[C++] multiset  (0) 2021.04.12

+ Recent posts