map?

1. 연관 컨테이너이며 노드 기반 컨테이너

2. 특정 정렬 기준으로 원소(Key)자동으로 정렬된다.

3. 균형 이진트리의 장점을 가지고 있어 찾기, 삭제, 삽입성능 O(lgN)을 보장한다.

 

자주 사용하는 멤버 함수

기본적으로 연관 컨테이너 set과 똑같지만 차이점을 주자면

[] 연산자를 제공한다.

[] 연산자를 이용해서 원소(key, value)를 삽입하거나, key에 매핑된 value를 갱신할 수 있다.

아래에는 자주 사용하는 부분들을 정리했다.

insert(pair<int,int>(key,value)) map에 pair 객체로 key와 value를 저장한다.
map[key] = value map에 key에 해당하는 원소가 없다면 (key, value) 저장하고
key에 해당하는 원소가 있다면 value를 갱신한다.
erase() 삭제
find() 찾기
lower_bound() key를 처음 찾는 곳의 반복자를 리턴
upper_bound() key 다음 원소의 반복자를 리턴
equal_range() [lower_bound(), upper_bound()) 를 pair 쌍으로 반복자를 리턴

연습해보기

삽입은 insert() 혹은 [] 연산자를 이용해서 삽입한다.

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

int main()
{
	map<int, int> m; // (key, value) less <

	// 임시 pair 객체 생성 후 저장
	m.insert(pair<int, int>(5, 100));
	m.insert(pair<int, int>(3, 100));
	m.insert(pair<int, int>(8, 30));
	m.insert(pair<int, int>(4, 40));
	m.insert(pair<int, int>(1, 70));
	m.insert(pair<int, int>(7, 100));

	pair<int, int> pr(9, 50);
	m.insert(pr);

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

	// iterator는 -> 연산자 오버로딩되어 있으므로
	// 포인터처럼 멤버를 -> 연산자로 접근이 가능
	for (auto iter = m.begin(); iter != m.end(); iter++)
		cout << "(" << iter->first << ',' << iter->second << ") ";
	cout << '\n';

	pair<map<int, int>::iterator, bool> ret;
	ret = m.insert(pair<int, int>(9, 50));
	if (true == ret.second)
		cout << "key: " << ret.first->first << ", value: " << ret.first->second << " success\n";
	else
		cout << "key: " << ret.first->first << ", value: " << ret.first->second << " failure\n";

	return 0;
}
(1, 70) (3, 100) (4, 40) (5, 100) (7, 100) (8, 30) (9, 50)
(1, 70) (3, 100) (4, 40) (5, 100) (7, 100) (8, 30) (9, 50)
key: 9, value : 50 failure
#include <iostream>
#include <map>
using namespace std;

int main()
{
	map<int, int> m;

	m[5] = 100; // key 5, value 100 insert
	m[3] = 100;
	m[8] = 30;
	m[4] = 40;
	m[1] = 70;
	m[7] = 100;
	m[9] = 50;

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

	m[5] = 200; // update;

	for (auto iter = m.begin(); iter != m.end(); iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';
	return 0;
}
(1, 70) (3, 100) (4, 40) (5, 100) (7, 100) (8, 30) (9, 50)
(1, 70) (3, 100) (4, 40) (5, 200) (7, 100) (8, 30) (9, 50)

검색은 find() 연산을 이용한다.

key에 해당하는 원소가 존재한다면 그 위치의 반복자를 리턴하고 아니라면 end() 반복자를 리턴한다.

lower_bound()는 key를 처음으로 찾은 지점의 반복자를 리턴

upper_bound()는 key를 찾은 다음 원소의 반복자를 리턴

equal_range()[lower_bound(), upper_bound()) 를 반복자를 쌍으로 리턴한다.

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

int main()
{
	map<int, int> m;

	m[5] = 100;
	m[3] = 100;
	m[8] = 30;
	m[4] = 40;
	m[1] = 70;
	m[7] = 100;
	m[9] = 50;

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

	// key를 기준으로 찾는다.
	auto iter = m.find(5);
	if (iter != m.end())
		cout << "key 5에 매핑된 value: " << iter->second << '\n';
	else
		cout << "key 5가 없습니다.\n";

	map<int, int>::iterator lower_iter = m.lower_bound(5);
	map<int, int>::iterator upper_iter = m.upper_bound(5);

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

	auto iter2 = m.equal_range(5);
	cout << "[iter2.first, iter.second): ";
	for (auto iter = iter2.first; iter != iter2.second; iter++)
		cout << "(" << iter->first << "," << iter->second << ") ";
	cout << '\n';
	return 0;
}
(1, 70) (3, 100) (4, 40) (5, 100) (7, 100) (8, 30) (9, 50)
key 5에 매핑된 value : 100
[lower_iter, upper_iter): (5, 100)
[iter2.first, iter.second): (5, 100)

정렬 기준을 변경해보자.

map도 기본적인 정렬 기준은 less이지만

key를 내림차순으로 정렬하고 싶다면 greater로 변경해서 사용하면 된다.

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

int main()
{
	// greater >
	map<int, string, greater<int>> m;

	// [] operator
	m[2] = "two";
	m[5] = "five";
	m[3] = "three";
	m[8] = "eight";
	m[4] = "four";
	m[6] = "six";
	m[1] = "one";
	m[7] = "seven";
	m[9] = "nine";

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

	for (int i = 0; i < m.size(); i++)
		cout << m[i] << ' ';
	cout << '\n';
	return 0;
}
(9, nine) (8, eight) (7, seven) (6, six) (5, five) (4, four) (3, three) (2, two) (1, one)
one two three four five six seven eight nine

 

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

[C++] stoi  (0) 2021.05.06
[C++] multimap  (0) 2021.04.28
[C++] unordered_set  (0) 2021.04.12
[C++] multiset  (0) 2021.04.12
스택의 응용 - 후위 표기식  (0) 2021.04.09

풀이

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

전체 점수, 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

한동안 엄청 고생했던 것을 바탕으로 작성한다.

 

Unity 상에서 Button에 이벤트를 할당하는 방법은 2가지가 있다.

직접 Inspector 창에서 매핑하기

스크립트 상에서 할당하기

using UnityEngine.UI;

GoPCBtn.onClick.AddListener(func);

 

보통은 매핑하기 힘들거나 코드를 한눈에 파악하기 힘들 때 사용하는 방법이 스크립트 상에서 할당하는 것이다.

가끔 Button 에 이벤트를 추가할 때 기존에 추가한 이벤트를 지우고 싶은 순간이 찾아오게 된다.

예를 들어 활성화될 때마다 리스너를 추가한다던가, 한 Popup 안에서 다양한 기능들을 스위칭해서 사용하는 경우

이때 사용하는 것이 바로 RemoveListener 혹은 RemoveAllListener이다.

 

알아둬야 할 점이 RemoveListener와 RemoveAllListener는 Editor에서 매핑시켜준 이벤트(메서드) 들은 제거되지 않고

스크립트 상에서 추가한 리스너들만 제거한다는 것이다.

 

Unity 레퍼런스에서도 읽어보면

런타임 중에 추가된 "비 영구" 리스너만 제거합니다. 영구 리스너들은 제거할 수 없습니다.

비 영구 리스너?

스크립트에 의해 추가 된 리스너들을 말한다.

 

영구 리스너

Editor 상에 Inspector에서 매핑시킨 리스너들은 Inspector 상에서만 제거할 수 있다.

 

결론

내가 스크립트에서 리스너를 할당했으면 RemoveListener 나 RemoveAllListener를 이용하고,

Inspector 창에서 리스너를 할당했으면 Inspector 창에서 지워야 한다.

 

'Unity' 카테고리의 다른 글

SOLID, 객체지향 원칙  (0) 2021.08.15
[Unity] ios unitywebrequest.get  (0) 2021.06.28
[Unity] 소수점 처리  (0) 2021.04.14
[Unity] text component  (0) 2021.04.03
[Unity] RectTransform width height 변경하는 방법  (0) 2021.04.02

소수점 처리가 필요한 경우는 아래와 같이 장인의 기운을 표시할 때 사용할 수 있다.

이럴 때 바로 포맷 함수를 이용하면 편하다.

 

장인의 기운이 30.13333 이라고 할 때

다음과 같이 소수점 둘째 자리까지 표기는 아래와 같이 하면 된다.

using System;

craftsmanship.text = string.Format("{0:0.##}", 표기할 수치) + "%";

Format

콜론 앞에 있는 부분은 Format에 들어갈 매개변수

 

콜론 뒤에 부분은 소수점 자릿수를 표시

 

'#'은 값이 있으면 표시하고 없으면 표시하지 않는다.

'Unity' 카테고리의 다른 글

SOLID, 객체지향 원칙  (0) 2021.08.15
[Unity] ios unitywebrequest.get  (0) 2021.06.28
[Unity] RemoveListener, RemoveAllListener  (0) 2021.04.15
[Unity] text component  (0) 2021.04.03
[Unity] RectTransform width height 변경하는 방법  (0) 2021.04.02

풀이

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

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

 

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

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

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

 

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

{ { 나 } } 는 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

+ Recent posts