2022. 5. 12. 13:10ㆍTIL💡/Algorithms
Greedy Algorithm
- 결정해야할 때 그 순간에 가장 좋다고 생각하는 것(기준이 필요)을 선택하면서 답을 찾아가는 알고리즘
- 그 때는 최적일지도 모르지만 최종적으로는 답이 최적이 아닐 수도 있다.
- 기준을 하나 선택해서 계속 적용하는 것
- 왜 그 기준을 선택하는 것이 정답인지를 증명해야 한다.
그리디 알고리즘의 흔한 예시인 거스름돈 문제는 사실 동전이 배수 관계이기 때문에 성립 가능하다.
💚1080 행렬
결국에는 0, 1밖에 없으므로 궁극적으로 연산을 아예 안 하는 것과 1회 한 것으로 나눌 수 있다.
3 X 3보다 큰 행렬을 바꾸는 연산을 할 때 가장자리가 연산횟수가 1회로 가장 적다.
그걸 기준으로 바꾸는 연산을 할지 말지 결정해야 한다.
행렬을 순회하면서 가장자리의 정보를 가지고 판단한다.
💚2138 전구와 스위치
위 문제와 동일하게 전구 on / off 둘로 나눌 수 있다.
💚1285 동전 뒤집기
무작위로 행과 열을 뒤집는다 생각하면 어렵다.
행과 열을 분리한 뒤, 행의 뒤집는 여부가 결정되어있으면 열의 경우 최대값을 만들 수 있는 조합이 제한되므로 최대값을 찾기 수월해진다.
그리고 그러한 행 조합을 모두 수행한 뒤 최대값을 찾으면 된다.
여기서는 비트마스크를 통해 행의 뒤집기 여부를 생성하였다.
#include <iostream>
#include <vector>
using namespace std;
char flip(char x) {
if(x == 'H') return 'T';
else return 'H';
}
int main() {
int n;
cin >> n;
vector<string> a(n);
for(int i = 0;i < n;i++) {
cin >> a[i];
}
int ans = n * n;
// 행 조합 생성
// 행마다 어떻게 돌릴지 결정한 후에 열마다 결정(즉 결정읇 분리)
for(int state = 0;state < (1 << n);state++) {
int sum = 0;
for(int j = 0;j < n;j++) {
int cnt = 0;
// 현재 열 j의 행 탐색
for(int i = 0;i < n;i++) {
char cur = a[i][j];
if(state & (1 << i)){
cur = flip(cur);
}
if(cur == 'T') {
cnt++;
}
}
// cnt = T의 개수, n - cnt = H의 개수
// 해당 열에서의 가능한 최소값
sum += min(cnt, n - cnt);
}
// 행 조합마다 T의 최소값 탐색
if(ans > sum) ans = sum;
}
cout << ans << '\n';
return 0;
}
💚1202 보석 도둑
오랜만에 확인한 Lower bound, Upper bound!! 잊지말고 잘 활용하자.
목표: 가격이 큰 보석을 최대한 많이 가져가는 것이 좋다. ➡️ 가격 기준 내림차순 정렬이 필요해 보인다.
무게와 가격 정보가 매칭되어야 한다. ➡️ pair를 쓰면 좋아보인다.
적게 넣을 수 있는 가방부터 챙겨서 넣는 게 이로워보인다.➡️ 최대 무게 기준 오름차순 정렬이 필요해 보인다.
총 2가지 방법이 있다.
1)
- 가격이 높은 보석부터 차례대로 각 보석을 담을 수 있는 가방 중 C[i]가 가장 작은 가방에 넣는다.
- 이를 구현하기 위해 다음을 효율적으로 할 수 있는 자료구조가 필요해 보인다.
1. 어떤 수 x보다 큰 숫자 중에 가장 작은 수를 찾는다.(lower_bound)❤️
2. 수를 지운다.
BST(Binary Search Tree)를 이용하면 1번과 2번을 O(lgK)만에 할 수 있다.
MultiSet을 이용한다.(NlogK)
⭐️ Set이란?
연관 컨테이너, 노드 기반 이진 트리 구조
중복 허용 안함(unique)
Set은 Red-Black Tree로 구현한다. 이는 BST 중 Self-Balancing 특성을 갖는다.
균형을 유지하기 때문에 모든 작업의 시간 복잡도가 O(NlogN)을 유지한다.
⭐️ 내림차순 정렬 set 생성
set<int, greater<int>> s;
⭐️ Iterator
for(auto i = s.begin();i != s.end();i++) {
cout << *i << endl;
}
⭐️ 멤버 함수
1) 크기 관련
- s.size()
- s.empty()
2) 탐색 관련
- s.lower_bound(k) : iterator 리턴
- s.upper_bound(k) : 정렬된 순서를 기준으로 k 다음 원소의 Iterator를 리턴
- s.find(k): k를 가리키는 iterator 리턴, k가 없으면 end()와 같은 iterator 리턴
3) 원소 삭제
- s.erase(it)
- s.erase(it1, it2)
- s.clear()
4) 원소 삽입
- s.insert(k)
🌎 추가 설명
insert 함수의 반환값으로 <iterator, bool>
- iterator: 삽입한 위치에 해당하는 iterator
- bool: 성공 여부
⭐️ Multiset이란?
Set과 거의 동일하지만, 중복된 key값을 허용하는 컨테이너이다.
#include <set>
multiset<int> mts;
multiset<int, greater<int>> mts;
multiset<int> mts(mts2);
multiset<int> mts2(mts1.begin(), mts1.end());
⭐️ 멤버 함수
거의 유사하지만 일부 다른 메서드가 있다.
- mts.lower_bound(k): 원소 k가 있는 구간의 처음을 가리키는 iterator 리턴
- mts.upper_bound(k): 원소 k가 있는 구간의 끝(그 다음 원소)을 가리키는 iterator 리턴
- mts.equal_range(k): 원소 k가 있는 구간을 나타내는 pair 객체 리턴. pair의 원소는 (lower_bound, upper_bound)
Set, Multiset 참고
https://choiiis.github.io/cpp-stl/basics-of-set-multiset-class/
[C++] set, multiset 클래스 정리
set? / set 생성자와 초기화/ set의 함수들 그리고 multiset? / multiset 생성자와 초기화/ multiset의 함수들
choiiis.github.io
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
struct jewel {
int m, v;
};
int main() {
int n, k;
cin >> n >> k;
vector<jewel> a(n);
for(int i = 0;i < n;i++) {
cin >> a[i].m >> a[i].v;
}
multiset<int> s;
for(int i = 0;i < k;i++) {
int t;
cin >> t;
s.insert(t);
}
// 가격 오름차순 정렬
sort(a.begin(), a.end(), [](jewel u, jewel v) {
return u.v > v.v;
});
long long ans = 0;
for(int i = 0;i < n;i++){
// 보석의 무게를 감당할 수 있는 가방 중 최소
auto it = s.lower_bound(a[i].m);
if(it != s.end()) {
// 가방 존재하면 보석 포함 후 해당 가방은 multiset에서 제거
ans += a[i].v;
s.erase(it);
}
}
cout << ans << '\n';
return 0;
}
2)
보석과 가방을 하나로 합치고 무게를 기준으로 오름차순 정렬하자.
최대힙(max heap, c++에서는 priority_queue)을 이용해 구현을 할 수 있다.
struct jewel {
int m,v,w;
}
m은 무게, v는 가격을 의미하고, w는 0이면 보석 1이면 가방임을 표시한다.
이건 (N + K) log N이라는 시간이 걸린다.
💚 2109 순회 강연
위의 보석 도둑 문제와 상당히 유사하다.
보석의 무게 <= 가방의 무게
현재 날짜 <= 강연의 날짜
부등호의 방향만 다르다고 생각하면 된다.
강연날짜를 기준으로 내림차순으로 정렬한 후
해당 날짜가 가능한 강연 중 가장 보상이 높은 강연 선택
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
struct Lecture {
// 강연료, 날짜
int p, d;
};
int main() {
int n;
cin >> n;
vector<Lecture> a(n);
for(int i =0;i < n;i++) {
cin >> a[i].p >> a[i].d;
}
// 날짜 내림차순
// 비교적 경쟁률이 낮은 것부터 하기 위함
sort(a.begin(), a.end(), [](Lecture a, Lecture b) {
return a.d > b.d;
});
int k = 0;
priority_queue<int> q;
int ans = 0;
for(int i = 10000;i >= 1;i--) {
// 인덱스 처리 및 i 날짜에 해당되는 강연 탐색
while(k < n && a[k].d == i) {
q.push(a[k].p);
k += 1;
}
if(!q.empty()) {
ans += q.top();
q.pop();
}
}
cout << ans << '\n';
return 0;
}
이전에는 가격 기준으로 정렬해놓은 후 해당 날짜 뒤에서부터 채워넣은 방식을 썼다.
#include <iostream>
#include <queue>
using namespace std;
priority_queue <pair<int, int>> q;
int scheduler[10001];
int main(void) {
int money = 0;
int n;
int p,d;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> p >> d;
q.push(make_pair(p, d));
}
while (!q.empty()) {
p = q.top().first;
d = q.top().second;
q.pop();
for (int i = d; i > 0; i--) {
if (scheduler[i] != 1) {
//cout << p << " " << i << endl;
money += p;
scheduler[i] = 1;
break;
}
}
}
cout << money << '\n';
}
💚 12738 가장 긴 증가하는 부분 수열3
숫자가 지나치게 길기 때문에 DP는 불가능하다.(DP로 푸는 건 여기, 숫자가 커서 O(N^2)의 시간복잡도를 지닌 DP는 바로 시간초과가 뜬다.)
LIS
가능한 정답의 리스트를 만들어가면서 답을 구한다.
그런데 가장 길이가 긴 리스트를 탐색하다보면 길이가 짧은 것은 탐색할 필요가 없다는 사실을 깨우친다.
lower_bound를 사용해서 크거나 같은 것중에 가장 작은 수를 의미하므로 lower_bound의 수를 바꿔주고, 없으면 추가하는 것을 반복하면 된다! 대신 길이만! 구할 수 있다.
예를 들어 5, 6, 7, 1인 경우 탐색이 끝나면 결국 1,6,7이 되기 때문
수열까지 다 구하려면 추가적인 작업이 필요하다. 각각의 수를 추가할 때마다 그 앞에 어떤 수의 인덱스가 있었는지 기록해야 한다.
앞선 예에 적용해보면 5을 입력할 때는 -1을, 6을 입력할 때는 0을...이렇게!
그리고 마지막 숫자부터 역으로 추적하여 증가수열을 찾아낼 수 있다.
시간 복잡도는 O(NlogN)이다.
#include <iostream>
#include <vector>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> v;
for(int i = 0;i < n;i++) {
int num;
cin >> num;
auto it = lower_bound(v.begin(), v.end(), num);
if(it == v.end()) {
v.push_back(num);
}
else {
*it = num;
}
}
cout << v.size() << '\n';
}
그리디 연습, 도전에서 이어질 예정...
'TIL💡 > Algorithms' 카테고리의 다른 글
[백준] 나머지 합 (0) | 2022.05.12 |
---|---|
[중급 알고리즘] 분할 정복, 정렬 (0) | 2022.05.12 |
[백준] 11053 가장 긴 증가하는 부분 수열 (0) | 2022.05.12 |
[프로그래머스] 정수 삼각형 (0) | 2022.05.12 |
[중급 알고리즘] BFS (0) | 2022.05.11 |