풀이

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

문제에서 주어진 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;
}

 

풀이

지문이 엄청 길지만 확실하게 정리하면 다음과 같다.

전체 점수, 3점 투표수, 2점 투표수가 모두 같으면 회장을 결정할 수 없다.

전체 점수, 3점 투표수가 같으면 2점 투표수가 많은 학생이 회장이 된다.

전체 점수가 같으면 3점 투표수가 많은 학생이 회장이 된다.

 

이렇게 정렬 기준을 잡고 정렬한 다음에 첫 번째 학생과 두 번째 학생을 비교하면 해결할 수 있다.

비교하는 이유는 전체 점수, 3점 투표수, 2점 투표수가 모두 같은 경우가 존재할 수 있기 때문에

한 번 더 확인해주고 판단하기 위함이다.

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

class Student
{
public:
	int idx;
	int total;
	int point3;
	int point2;

	Student() : idx(0), total(0), point3(0), point2(0) {}
	Student(int idx, int a, int b, int c) : idx(idx), total(a), point3(b), point2(c) {}
	bool operator<(const Student& other)
	{
		if (total == other.total)
		{
			if (point3 == other.point3)
			{
				return point2 > other.point2;
			}
			return point3 > other.point3;
		}
		return total > other.total;
	}

	bool operator==(const Student& other)
	{
		if (total == other.total && point3 == other.point3 && point2 == other.point2)
			return true;
		else
			return false;
	}
};

void addPoint(vector<Student>& v, int idx, int point)
{
	if (point == 3)
		v[idx].point3++;
	else if (point == 2)
		v[idx].point2++;

	v[idx].total += point;
}

int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0);

	int n;
	cin >> n;
	vector<Student> v(3);

	for (int i = 0; i < 3; i++)
		v[i].idx = i + 1;

	for (int i = 0; i < n; i++)
	{
		int a, b, c;
		cin >> a >> b >> c;

		addPoint(v, 0, a);
		addPoint(v, 1, b);
		addPoint(v, 2, c);
	}

	sort(v.begin(), v.end());

	if (v[0] == v[1])
		cout << 0 << ' ' << v[0].total << '\n';
	else
		cout << v[0].idx << ' ' << v[0].total << '\n';
	return 0;
}

정렬을 하다 보면 이런 문제들이 있다.

www.acmicpc.net/problem/10814

 

10814번: 나이순 정렬

온라인 저지에 가입한 사람들의 나이와 이름이 가입한 순서대로 주어진다. 이때, 회원들을 나이가 증가하는 순으로, 나이가 같으면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 프로그램을

www.acmicpc.net

나이가 증가하는 순서로 정렬을 하지만 만약에 나이가 같다면 먼저 가입한 사람이 앞에 오는 순서로 정렬하는 문제

이러한 문제처럼 먼저 들어온 사람이 먼저 출력되는 것을 아래와 같이 정리할 수 있다.

정렬되지 않은 상태에서 같은 키(나이) 값을 가진 원소의 순서가 정렬 후에도 유지가 되는가?

이러한 개념이 바로 Stable Sort 이라고 하며 안정된 정렬이라고 불린다.

Stable Sort는 정렬이 되어서도 같은 키값이라도 먼저 들어온 녀석이 먼저 앞에 있는다.

같은 키를 가진 사람이 A B 가 있는데, 정렬 후에도 이 순서는 유지가 된다는 것이다.

 

반대로 Unstable Sort불안정된 정렬이며 정렬 후에는 이 순서가 무너질 수 있다.

같은 키를 가진 사람이 A B 순서로 도착했지만 어떠한 경우에서 B A 순서가 될 수 있다.

먼저 온 A 입장에서는 억울한 경우이다.

 

이처럼 문제에서 안정된 정렬을 요구하는 문제들이 있다.

이러한 안정된 정렬을 알고리즘에서 써먹으려면 어떻게 할까?

STL에서는 stable_sort를 따로 지원해준다.

https://en.cppreference.com/w/cpp/algorithm/stable_sort

이 코드를 따라 쳐보면 다음과 같이 안정된 정렬이 출력되는 것을 확인할 수 있다.

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

struct Employee
{
	int age;
	string name;
};

bool operator<(const Employee& a, const Employee& b)
{
	return a.age < b.age;
}

int main()
{
	vector<Employee> v =
	{
		{27, "Uade"},
		{19, "Dopa"},
		{27, "Papa"}
	};

	// 안정 정렬
	stable_sort(v.begin(), v.end());

	for (const Employee& e : v)
		cout << e.age << ' ' << e.name << '\n';
	return 0;
}
19 Dopa
27 Uade
27 Papa

출력 결과를 보면 같은 나이 27살 친구가 "Uade"와 "Papa"가 있는데

먼저 들어온 친구의 이름은 "Uade" 이므로

같은 나이 경우에서는 "Uade" 가 먼저 나오는 것을 확인할 수 있다.

 

알고리즘 파트를 공부하면 많은 정렬법에 대해서 배우게 된다.

여기서도 안정된 정렬과 불안정된 정렬을 구분할 수 있다.

 

안정한 정렬 Stable Sort

Insertion Sort 삽입 정렬 정렬되지 않은 원소를 선택해서 정렬된 자리를 잘 찾아서 삽입하는 알고리즘이다. 이 때 Swap 과정이 일어나지 않으므로 안정된 정렬이다.
Merge Sort 병합 정렬  크기가 N인 배열을 1 / 2 씩 쪼개면서 분할(divide) 과정을 반복한다. 이 반복은 배열의 크기가 1이 될 때까지 반복한다.
분할 과정이 끝났다면 정복(Conquer) 과정을 통해 정렬된 배열을 합친다. 이 때 Swap 과정이 일어나지 않으므로 안정된 배열이다.
Bubble Sort 버블 정렬 두 개의 원소를 비교하면서 앞의 원소가 뒤의 원소보다 크다면 Swap 한다. 여기서의 Swap 은 자신과 왼쪽 혹은 오른쪽과 Swap 하는 과정이기 때문에 안정성을 깨트리지 않는 정렬이다. 그렇게 비교하면서 작은 원소를 앞으로 보내는 정렬이므로 안정된 정렬이다.
Counting Sort 카운팅 정렬 카운팅 정렬은 메모리 공간을 사용해서 해당 원소가 등장하면 그 등장한 횟수만큼 메모리에 저장했다가 순서대로 꺼내서 출력하여 정렬을 완성하는 정렬법이다. 저장했다가 순서대로 꺼내기 때문에 안정된 정렬이다.

불안정한 정렬 Unstable Sort

Selection Sort 선택 정렬 가장 작은 원소를 찾아서 가장 알맞은 위치를 찾아 그 위치와 현재 위치를 Swap 해가며 정렬해간다. 이 과정에서 안정성을 깨트리므로 불안정한 정렬이다.
Heap Sort 힙 정렬 Min-heap 을 기준으로 생각해보자.
이미 Heap에 새로운 원소가 들어온다고 했을 때
아래 말단 노드부터 루트 노드까지 부모 노드와 비교하면서 들어온 노드가 부모 노드보다 더 작은 노드라면 Swap 하는 과정이 들어가게 된다. 이 때 이미 같은 노드의 값이 미리 Heap에 들어있는데 들어온 노드가 어쩌다보니 루트 노드의 자식 노드까지 들어가게 된 경우 배열 index 기준으로 먼저 들어온 값의 index 보다 나중에 들어온 index 가 역전되는 현상이 생긴다. 따라서 불안정한 정렬이다.
Quick Sort 퀵 정렬 가장 앞의 원소를 pivot 으로 잡고 생각해보자.
pivot 을 기준으로 left 포인터는 pivot 보다 큰 원소를 찾고
right 포인터는 pivot 보다 작은 원소를 찾아 left 와 right 를 swap 해가면서 정렬을 한다. 이 경우에서 Swap으로 인해 안정성을 깨트리므로 불안정한 정렬이다.

 

In place Sort

추가적으로 정렬 알고리즘을 공부하다 보면 In-place Sort라는 용어를 듣게 된다.

In-place라는 말은 정렬을 하는 과정에서 추가적인 메모리를 사용하지 않는 것을 의미한다.

 

대표적으로 Bubble Sort 경우 추가적인 메모리 필요 없이 인접한 원소를 끼리 비교하고 서로 Swap 하므로

메모리 공간의 복잡도는 O(1) 이 된다.

 

반대로 Merge Sort 경우 대표적인 Not in place이다.

합병 정렬하는 과정에서 N개의 Problem을 1 / 2 로 쪼개 각각의 SubProblem을 분할해가며,

나중에 정렬된 두 개의 SubProblem 을 Merge 하는 과정에서 추가적인 메모리 공간이 필요하게 된다.

메모리 공간의 복잡도는 O(N)이다.

 

In place Sort

Insertion Sort O(1) 메모리 공간이 필요한 경우는 정렬되지 않은 원소를 하나 선택하게 되는데 이 때 값을 기억하는 공간이 필요하다.
이후 그 자리를 만들어주도록 원소들이 한 칸씩 오른쪽으로 이동하고 기억한 공간을 그 원소에 삽입하면서 정렬한다.
Selection Sort O(1) 작은 원소를 찾아서 정렬하려는 위치와 Swap한다.
Swap 하는 과정에서 Temp 라는 공간이 필요하다.
Bubble Sort O(1) 인접한 두 원소를 Swap 하는 과정에서 Temp 라는 공간이 필요하다.
Heap Sort O(1) 부모 노드와 자식 노드를 비교하는 과정에서 만약 Swap이 필요하다면 Temp 라는 공간이 필요하다.
Quick Sort O(1) 우선 pivot 이라는 공간과 left right 서로 Swap 할 Temp 공간이 필요하다.

Not In place Sort

Merge Sort O(N) Merge 하는 과정에서 추가적인 메모리 공간이 필요하다.
Counting Sort O(N) 정렬 기법중 선형 시간안에 정렬할 수 있지만
추가적인 메모리 공간이 필요한 정렬기법이다.
공간을 사용하므로써 시간을 줄이는 기법이다.
Radix Sort O(N) Bucket 이라는 공간을 사용한다. 추가적인 메모리 공간이 필요한 정렬 기법이다.

 

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

수학 관련  (0) 2021.08.18
백트래킹 (Backtracking)  (0) 2021.07.28
파라메트릭서치 (Parametric Search)  (0) 2021.05.09
[cmath] math 관련 함수들  (0) 2021.05.07
[GCD, LCM] 유클리드 호제법  (0) 2021.05.07
문제 풀이

 

힙 2개를 이용하면 쉽게 해결할 수 있다.

각 각의 최대 힙과 최소 힙을 준비하자. 그리고 규칙에 따라 들어오는 input을 처리하면 된다.

 

1. max-heap 크기 > min-heap 크기

만족하면 min-heap에 넣는다.

아니라면 max-heap에 넣는다.

 

2. max-heap의 루트 노드 > min-heap의 루트 노드

만족하면 서로 swap 한다.

 

3. 홀수 번째 수를 읽을 때마다 중앙값을 처리해야 하므로,

홀수 번째이면 그때 max-heap의 루트 노드를 반환하면 된다.

 

문제의 예시를 보면 다음과 같이 흘러간다.

 

1이 들어오면

max-heap 1                
min-heap                  

 

5가 들어오면

max-heap 1                
min-heap 5                

 

4가 들어오면

max-heap 4 1              
min-heap 5                

 

3이 들어오면

max-heap 4 1              
min-heap 3 5              

규칙 2에 위반하므로, 서로 Swap 해준다.

max-heap 3 1              
min-heap 4 5              

2가 들어오면

max-heap 3 2 1            
min-heap 4 5              

빨간색으로 표시한 부분이 홀수 번째를 읽었을 때 중앙값이다.

 

소스 코드
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(0); cin.tie(0);
	int tc;
	cin >> tc;
	while (tc--)
	{
		int m;
		cin >> m;

		priority_queue<int> pq_max;
		priority_queue<int, vector<int>, greater<int>> pq_min;
		vector<int> ret;
		for (int i = 1; i <= m; i++)
		{
			int x;
			cin >> x;

			if (pq_max.size() > pq_min.size())
				pq_min.push(x);
			else
				pq_max.push(x);

			if (!pq_max.empty() && !pq_min.empty())
			{
				if (pq_max.top() > pq_min.top())
				{
					int a = pq_max.top(); pq_max.pop();
					int b = pq_min.top(); pq_min.pop();
					pq_max.push(b);
					pq_min.push(a);
				}
			}

			if (i % 2 == 1)
			{
				ret.push_back(pq_max.top());
			}
		}

		cout << ret.size() << '\n';
		for (int i = 0; i < ret.size(); i++)
		{
			if ((i + 1) % 10 == 0)
				cout << ret[i] << '\n';
			else
				cout << ret[i] << ' ';
		}
		cout << '\n';
	}
	return 0;
}

풀이

생각보다 고생한 문제!!

 

주어진 문자열의 길이가 100만이기 때문에 여기서 빈번한 삭제와 삽입이 필요하므로

list 자료구조를 이용해야 한다.

 

문제는 list에 대해서 얄팍하게 알고 있다면 크게 디일 수 있다..

 

문제에서는 커서를 이용해서 옮겨 다니며 리스트에 해당 문자를 삽입하거나 삭제하는 구조로 되어있다.

따라서 처음에 cursor를 list의 첫 번째 반복자로 할당해놓고 양방향 반복자이므로 뒤로 갔다 앞으로 갔다 하면서 주어진 문제에 맞게 동작을 처리하면 된다.

 

'<'

cursor 왼쪽으로

if (s[i] == '<')
{
	if (cursor != lt.begin())
		cursor--;
}

'>'

cursor 오른쪽으로

else if (s[i] == '>')
{
	if (cursor != lt.end())
		cursor++;
}

'-'

cursor 한 칸 왼쪽으로 당겨서 지워주고 cursor의 위치를 그 다음 위치로 옮겨야 한다.

문제에서 만약 커서의 위치가 줄의 마지막이 아니라면,

커서 및 커서 오른쪽에 있는 모든 문자는 오른쪽으로 한 칸 이동하라는 말이 있다.

else if (s[i] == '-')
{
	if (cursor != lt.begin())
		cursor = lt.erase(--cursor);
}

아무것도 아니라면 list에 삽입해주면 된다.

else
{
	lt.insert(cursor, s[i]);
}

 

헷갈린 나를 위해서 정리본

iter2 = lt.insert(iter1, data);
iter1 자리에 data를 삽입한다.
iter1은 삽입한 다음 위치를 가리킨다.
iter2는 삽입한 그 위치를 가리킨다.

iter2 = lt.erase(iter1);
iter1 자리에 data를 삭제한다.
iter2는 삭제된 다음 위치를 가리킨다.
#include <iostream>
#include <list>
#include <string>
using namespace std;

int main()
{
	ios_base::sync_with_stdio(0); cin.tie(0);
	int tc;
	cin >> tc;
	while (tc--)
	{
		string s;
		list<char> lt;
		auto cursor = lt.begin();

		cin >> s;

		for (int i = 0; i < s.length(); i++)
		{
			if (s[i] == '<')
			{
				if (cursor != lt.begin())
					cursor--;
			}
			else if (s[i] == '>')
			{
				if (cursor != lt.end())
					cursor++;
			}
			else if (s[i] == '-')
			{
				if (cursor != lt.begin())
					cursor = lt.erase(--cursor);
			}
			else
			{
				lt.insert(cursor, s[i]);
			}
		}

		for (auto c : lt)
			cout << c;
		cout << '\n';
	}
	return 0;
}

풀이

뱀이 이동할 때를 생각해보자.

이동할 때 머리를 쭉 늘려 전진하고 뒤의 꼬리를 땡겨온다.

자료구조 deque 를 사용하면 편하게 구현할 수 있다. (앞 뒤로 땡기거나 늘릴 수 있기 때문)

 

뱀이 이동하기 때문에 1초가 소요된다.

아래의 로직들은 1초 동안에 수행되는 행동들이다.

뱀이 움직이는 방향을 아래와 같이 정의할 수 있다.

dir = 0 오른쪽
dir = 1 아래쪽
dir = 2 왼쪽
dir = 3 위쪽

int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };

dir 방향대로 움직인다고 했을 때 다음칸을 확인해보고 움직임을 결정하면 된다.

 

사과가 있는 칸인 경우

해당 칸을 먹었다고 표시해주고, 뱀의 머리를 늘려주면 된다.

이때 꼬리는 자르지 않는다.

 

사과가 없는 칸인 경우

해당 칸에 아무것도 없으므로 뱀의 머리를 늘려주고 꼬리를 잘라준다.

뱀이 이동할 때를 생각하면 머리를 쭉 앞으로 늘리고 뒤에 꼬리를 땡기는 방식을 상상하면 이해하기 쉽다.

 

해당 칸이 벽이거나 자신의 몸통인 경우

벽인지 아닌지는 해당 좌표가 범위를 벗어났는지를 체크하면 되고

자신의 몸통인지 확인하는 방법은 deque 를 순회하면서 그 좌표와 일치하는 좌표가 있다면 몸통과 부딪힌 경우고

없다면 이동이 가능하다.

bool isValid(int nx, int ny)
{
	// 벽이거나
	if (nx <= 0 || ny <= 0 || nx > N || ny > N) return false;

	// 자신이면
	for (int i = 0; i < dq.size(); i++)
	{
		if (nx == dq[i].first && ny == dq[i].second)
			return false;
	}

	return true;
}

 

특정 시간에 방향을 변환하는 경우

뱀이 이동한 후에 시간을 보니 방향을 바꾸라는 시간과 일치한다면 방향을 바꿔주자.

방향을 자세히 보면 규칙이 있는데 규칙은 다음과 같다.

'D'인 경우
dir = (dir + 1) % 4;

'L'인 경우
dir = (dir + 3) % 4;

 

이제 기능들을 조합해서 문제를 해결하면 아래와 같다.

 

#include <iostream>
#include <deque>
#include <queue>
using namespace std;

// 0 우, 1 하, 2 좌, 3 상
int dx[] = { 0,1,0,-1 };
int dy[] = { 1,0,-1,0 };

int N, K, L;
int board[101][101];

queue<pair<int, char>> q;
deque<pair<int,int>> dq;

bool isValid(int nx, int ny)
{
	// 벽이거나
	if (nx <= 0 || ny <= 0 || nx > N || ny > N) return false;

	// 자신이면
	for (int i = 0; i < dq.size(); i++)
	{
		if (nx == dq[i].first && ny == dq[i].second)
			return false;
	}

	return true;
}

int main()
{
	ios_base::sync_with_stdio(0); cin.tie(0);

	cin >> N >> K;
	for (int i = 0; i < K; i++)
	{
		int x, y;
		cin >> x >> y;
		board[x][y] = 1; // apple
	}
	cin >> L;
	for (int i = 0; i < L; i++)
	{
		int x;
		char d;
		cin >> x >> d;
		q.push({ x,d });
	}

	// snake move
	int dir = 0; // 0 우, 1 하, 2 좌, 3 상
	int time = 0;
	int x, nx, y, ny;
	dq.push_back({ 1,1 });
	while(1)
	{
		time++;

		x = dq.front().first;
		y = dq.front().second;

		nx = x + dx[dir];
		ny = y + dy[dir];

		// 유효 검사
		if (isValid(nx, ny) == false) break;

		if (board[nx][ny] == 1)
		{
			board[nx][ny] = 0;
			dq.push_front({ nx,ny });
		}
		else
		{
			dq.push_front({ nx,ny });
			dq.pop_back();
		}

		// 방향 전환의 시간이 왔다면
		if (!q.empty() && time == q.front().first)
		{
			if (q.front().second == 'D')
				dir = (dir + 1) % 4;
			else
				dir = (dir + 3) % 4;

			q.pop();
		}
	}

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

 

'Algorithm' 카테고리의 다른 글

[백준 2696] 중앙값 구하기 (C++)  (0) 2021.04.22
[백준 5397] 키로거 (C++)  (0) 2021.04.21
[백준 1966] 프린터 큐 (C++)  (0) 2021.04.18
[백준 4889] 안정적인 문자열 (C++)  (0) 2021.04.13
[백준 2304] 창고 다각형 (C++)  (0) 2021.04.12

풀이

처음에 시간 초과가 난 문제.

이유는 현재 가장 높은 중요도 문서를 찾는데 O(N)를 추가로 구하기 때문이다.

생각해보니까. 우선순위 큐를 이용하면 O(N)에 찾는 것이 아닌 가장 위에 있는 원소만 확인해보면

O(1)에 된다는 것을 알았다.

 

기본적으로 우선순위 큐는 삽입할 때 알아서 우선순위대로 만들어주기 때문이다.

 

자료구조 2개를 써서 풀었다.

- 에는 (중요도, 문서 번호)

- 우선순위 큐에는 (중요도)

 

문서를 하나씩 앞에서 보면서 현재의 문서보다 더 높은 중요도 문서가 있으면

해당 문서를 큐 뒤에 보낸다.

 

아니라면 현재의 문서를 큐에서 pop 하고, 우선순위 큐에서도 pop 한다.

이때 내가 찾는 문서의 번호라면 그 값을 출력하면 된다.

 

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

int main()
{
	ios_base::sync_with_stdio(0); cin.tie(0);
	int tc;
	cin >> tc;
	while (tc--)
	{
		queue<pair<int, int>> q; // (priority, num)
		priority_queue<int> pq;  // priority
		int n, m;

		cin >> n >> m;
		for (int i = 0; i < n; i++)
		{
			int x;
			cin >> x;
			q.push({ x, i }); 
			pq.push(x);
		}

		int cnt = 0;
		while (!q.empty())
		{
			int now = q.front().first;
			int num = q.front().second;

			if (!pq.empty() && now < pq.top())
			{
				q.push(q.front());
				q.pop();
				continue;
			}

			cnt++;
			pq.pop();
			q.pop();
			if (m == num)
			{
				cout << cnt << '\n';
				break;
			}
		}
	}
	return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 5397] 키로거 (C++)  (0) 2021.04.21
[백준 3190] 뱀 (C++)  (0) 2021.04.20
[백준 4889] 안정적인 문자열 (C++)  (0) 2021.04.13
[백준 2304] 창고 다각형 (C++)  (0) 2021.04.12
[백준 2841] 외계인의 기타 연주 (C++)  (0) 2021.04.10

풀이

스택에 문자열을 하나씩 집어넣으면서 괄호 쌍("{}")이 맞다면 스택을 비워주고,

아니라면 계속해서 스택에 넣어준다.

 

모든 문자열을 다 순환했을 때,

스택이 비어있다면 주어진 문자열은 모두 안정적인 문자열이다.

스택이 비어있지 않다면 여는 괄호를 닫는 괄호로 바꾸거나, 닫는 괄호를 여는 괄호로 바꾸는 연산을 해야 한다.

 

몇 번 손으로 쓰다 보면 규칙을 찾을 수 있다.

{ { 나 } } 는 1번 만에 안정적인 문자열을 만들 수 있지만

} { 는 2번 연산을 적용해야 안정적인 문자열을 만들 수 있다.

 

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

int main()
{
	string s;
	int n = 0;
	int cnt = 0;
	while (getline(cin, s))
	{
		if (s[0] == '-')
			break;

		n++;
		cnt = 0;
		stack<char> st;
		for (int i = 0; i < s.length(); i++)
		{
			if (!st.empty() && st.top() == '{' && s[i] == '}')
				st.pop();
			else
				st.push(s[i]);
		}

		while (!st.empty())
		{
			char c1 = st.top(); st.pop();
			char c2 = st.top(); st.pop();

			if (c1 == c2)
				cnt++;
			else
				cnt += 2;
		}

		cout << n << ". " << cnt << '\n';
	}
	return 0;
}

'Algorithm' 카테고리의 다른 글

[백준 3190] 뱀 (C++)  (0) 2021.04.20
[백준 1966] 프린터 큐 (C++)  (0) 2021.04.18
[백준 2304] 창고 다각형 (C++)  (0) 2021.04.12
[백준 2841] 외계인의 기타 연주 (C++)  (0) 2021.04.10
[백준 10799] 쇠막대기 (C++)  (0) 2021.04.08

풀이

약간 도미노를 생각하면 편할 것 같다.

 

왼쪽부터 간다고 생각하면 다음과 같다.

3 ~ 4 의 구간은 비록 높이가 0인 다각형이지만 2 ~ 3 구간에서의 높이가 4이기 때문에 3 ~ 4 구간의 높이도 4가 된다.

5 ~ 6 의 구간은 비록 높이가 3인 다각형이지만 4 ~ 5 구간에서의 높이가 6이기 때문에 5 ~ 6 구간의 높이도 6이 된다.

즉 나보다 한 칸 옆에서의 높이가 나의 높이보다 크다면 그것으로 갱신해준다.

	for (int i = 1; i <= 1000; i++)
		area[i] = max(height[i], area[i - 1]);

 

오른쪽부터 간다고 생각하면 다음과 같다.

15 ~ 16 구간은 높이가 8인 다각형이지만 현재 area 는 10으로 되어있다. 따라서 더 작은 값으로 갱신하면 된다.

14 ~ 15 구간을 보자.

높이가 0인 다각형이지만 현재 area 는 10으로 되어있다.

문제에 따르면 이 구간의 높이는 8이 되야한다.

한 칸 오른쪽에서의 넓이 area가 현재 높이보다 크다면 더 큰 값인 10으로 갱신한다.

그리고 나서 현재 area 와 비교해서 더 작은 값으로 갱신하면 8이 된다.

7 ~ 8 구간을 보자.

현재의 높이는 0이고, 한 칸 오른쪽에서의 넓이 area가 현재 높이보다 크다면 더 큰 값인 10으로 갱신한다.

그리고 나서 현재 area와 비교해서 더 작은 값으로 갱신하면 6이 된다.

	for (int i = 1000; i >= 0; i--)
		area[i] = min(area[i], max(height[i], area[i + 1]));

 

왼쪽과 오른쪽을 통해서 얻은 area 를 다 더해주면 창고 다각형의 넓이를 구할 수 있게 된다.

 

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

int n;
int height[1001];
int area[1001];
int main()
{
	cin >> n;
	while (n--)
	{
		int L, H;
		cin >> L >> H;
		height[L] = H;
	}

	// left
	for (int i = 1; i <= 1000; i++)
		area[i] = max(height[i], area[i - 1]);

	// right
	for (int i = 1000; i >= 0; i--)
		area[i] = min(area[i], max(height[i], area[i + 1]));

	int ret = 0;
	for (int i = 1; i <= 1000; i++)
		ret += area[i];

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

+ Recent posts