no image
[백준-11000] 강의실 배정
11000번: 강의실 배정 그리디 "활동 선택 문제" 방식의 문제이다. 먼저 시간복잡도부터 살펴보면 1초의 제한시간에 input 의 길이가 20만이다. 따라서 제곱의 복잡도를 가지게 되면 터진다는 걸 알 수 있다. 이 점에 착안해 각 프로세스를 돌면서 log(n) 의 탐색 복잡도 로직으로 같이 이어서 들을 강의를 체킹하여 문제를 풀기로 하였다. 로직은 다음과 같다. 각 프로세스를 "시간 순 → 짧게 끝나는 순" 으로 정렬한다. 정렬한 프로세스를 하나씩 돌면서 나머지 프로세스 중 바로 시작할 수 있는 프로세스를 이분탐색을 통해 찾는다. (반복) 찾은 프로세스들을 모두 checking 해주고 다시 2번으로 돌아간다. 이에 대한 코드는 다음과 같다. #define _CRT_SECURE_NO_WARNINGS #..
2021.08.11
[프로그래머스] 큰수만들기
#include #include #include using namespace std; string solution(string number, int k) { string answer = ""; int window_size = number.size() - k; int max_idx = -1; char max_val; for(int i=0 ; i< window_size ; i++) { max_val = '0'; for(int j = max_idx + 1 ; j
2021.08.11
no image
[백준-1082] 방번호
1028번: 다이아몬드 광산 dp 문제였다. dp 배열에 문자열을 넣고 drop up 방식으로 단순하게 탐색하는 식으로 문제를 풀 수 있었다. 로직은 다음과 같다. dp[1] 부터 차례대로 값을 비교하되 하나의 dp 마다 n개의 수를 넣기 전의 dp 값과 비교하여 해당 수를 추가한 뒤 값이 더 큰 값을 dp 값으로 갱신하는 로직. 말이 좀 어려울 수 있겠지만 dp[i - (수의값)] 에서 해당 수를 첨가하여 커지나 안커지나만 확인하면 되는 문제였다. dp 라 푸는데 좀 오래 걸렸지만.. 다음에는 더 잘 기억하자 #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include #include #incl..
2021.08.10
no image
[백준-15684] 사다리 조작
15684번: 사다리 조작 이거는 약간 구현에 신경써야 하는 문제이다. 여러번 풀어보는 것도 나쁘지 않다고 생각한다. 일단 하나씩 다 돌아보는 단순한 브루트포스 알고리즘으로 풀리는 문제이다. 다만 각 가로줄들을 데이터로 잘 표현하여 탐색을 돌리는 구현이 깔끔하게끔 짜야하는 문제가 있다. 각 가로줄을 일일이 저장하는 것이 아닌 특정 점에서 밑으로 내려올 수 있다 없다 로 가로줄을 판단할 수 있다. 예를 들어 check[3][4]=1 이라면 (3,4) 에서 사다리 밑으로 내려올 수 있다는 의미가 될 수 있다. 이를 이용해 check[x][y], check[x][y-1], check[x][y+1] 이 3개의 값을 비교하여 1인 경우 dfs 탐색을 돌리는 식으로 로직을 구성하면 문제를 풀 수 있다. #defin..
2021.08.09
[백준-2805] 나무자르기
2805번: 나무 자르기 간단한 이분탐색 문제이다. 최대값을 right로 0을 left 로 설정한 후 calc 함수만 짠뒤 간단히 이분탐색을 돌리면 풀리는 간단한 문제이다. 다만 값의 범위가 int 값의 범위를 넘어가기 때문에 이를 long long 으로 변환해주어 적절하게 풀면 간단히 풀린다. #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include #include #include #include using namespace std; typedef pair pii; typedef tuple tii; typedef long long LL; LL n, m; LL arr[1000001]; LL c..
2021.08.08
no image
[백준-2517] 달리기
[https://www.acmicpc.net/problem/2517] 시간 복잡도가 매우 중요한 문제이다. 일반적인 생각으로 매 선수가 들어올 때마다 앞에서 몇번째인지 찾기 위해서는 "기존 입력을 받는 n" * "자기보다 작은 선수들 찾기 n" 가 되어 n^2 으로 시간이 터져버린다. 때문에 이 문제를 풀기 위해서는 최소한 nlogn 의 로직으로 풀어야 한다. 즉, 이분탐색이나 logn 복잡도의 탐색 알고리즘이 필요하다. 이를 위해 세그먼트 트리를 사용할 수 있다. 로직은 다음과 같다. 첫번째 선수 하나 들어왔을 때 세그먼트 트리에 체크해준다. 두 번째 선수 부터는 세그먼트 트리에 업데이트 하기전 자기보다 작은 값까지 몇까지 있는지 logn의 복잡도로 확인한다. 위 1, 2 번 방법을 반복한다. 즉, 세..
2021.08.08
no image
[백준-1202] 보석도둑
https://www.acmicpc.net/problem/1202 1202번: 보석 도둑 첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000) 다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000) 다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci www.acmicpc.net 위 문제의 경우 1개의 우선순위큐를 통해 구할 수 있다. 이런 그리디 문제가 나는 비슷한 문제가 많으니 기억해두자. 일단 입력 받은 가방과 보석을 모두 오름차순으로 정렬 한다. 그 후 작은 가방 하나씩 꺼내고, 보석 배열을 순회하면서 집어넣을 수 있는 보석을 우선순위 큐에 집어 넣는다. (by while) 다 집어 넣었으면 우선순위 큐의..
2021.07.13
[프로그래머스]타겟넘버
타겟넘버 이 문제는 dfs 로 풀었을 때보다 bfs 로 풀었을 때 시간 복잡도가 적게 나오는 문제이다. 출처 https://namu.wiki/w/BFS 해당 그림을 봤을 때는 속도가 비슷해 보이지만 기본적으로 재귀 함수 틀의 DFS 는 BFS 보다 검색 속도가 느리기 때문에 동일 방법을 사용해도 될 때 시간 단축을 위해서는 BFS 를 사용하는 것이 좋다. #include #include #include #include #include using namespace std; int _count = 0; void dfs(vector check, vector num, int target, int current, int idx, int _size) { if (target == current && _size == ..
2021.04.30
no image
완전탐색
재귀적 호출에 대한 개념을 먼저 잡고 넘어가자. 그 이유는 완전 탐색 시에, 해당 호출 방식을 자주 활용하기 때문이다. 재귀함수의 기본적인 이해 재귀함수란: 함수 내에서 자기 자신을 다시 호출하는 함수 : 자신이 수행할 작업을 유사한 형태의 여러 조각으로 쪼갠 뒤 그 중 한 조각을 수행하고, 나머지를 자기 자신을 호출해 실행하는 함수 재귀함수 호출방식 void RecursiveFunction(void) { printf("Recursive function example1 \n"); RecursiveFunction(); } ※ 기저 사례 (base case) ▷ 더 이상 쪼개지지 않는 가장 작은 작업, 즉 최소한의 작업에 도달했을 때 답을 곧장 반환하는 조건문에 포함될 내용 ▷ 기저 사례를 선택할 때는 존재..
2021.04.24
11000번: 강의실 배정

그리디 "활동 선택 문제" 방식의 문제이다.

먼저 시간복잡도부터 살펴보면 1초의 제한시간에 input 의 길이가 20만이다. 따라서 제곱의 복잡도를 가지게 되면 터진다는 걸 알 수 있다.

이 점에 착안해 각 프로세스를 돌면서 log(n) 의 탐색 복잡도 로직으로 같이 이어서 들을 강의를 체킹하여 문제를 풀기로 하였다.

로직은 다음과 같다.

  1. 각 프로세스를 "시간 순 → 짧게 끝나는 순" 으로 정렬한다.
  2. 정렬한 프로세스를 하나씩 돌면서 나머지 프로세스 중 바로 시작할 수 있는 프로세스를 이분탐색을 통해 찾는다. (반복)
  1. 찾은 프로세스들을 모두 checking 해주고 다시 2번으로 돌아간다.

이에 대한 코드는 다음과 같다.

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <tuple>
#include <string>
#include <string.h>
#include <stack>
#include <set>
#include <map>


using namespace std;
typedef long long LL;
typedef pair<int, int> pii;

int check[200001];
bool compare(pii a, pii b) {
	if (a.first == b.first) {
		return a.second < b.second;
	}
	else {
		return a.first < b.first;
	}
}

int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int n, a, b;
	cin >> n;
	vector<pii> cls;
	for (int i = 0; i < n; i++) {
		cin >> a >> b;
		cls.push_back({ a,b });
	}
	sort(cls.begin(), cls.end(), compare);

	vector<int> idx;
	for (int i = 0; i < cls.size(); i++)
		idx.push_back(cls[i].first);

	int res = 0;
	for (int i = 0; i < cls.size(); i++) {
		if (check[i] != 0) continue;
		else check[i] = 1;

		int cx = cls[i].first;
		int cy = cls[i].second;

		while (1) {
			// 제일 맞는 놈 바로 체킹 -> log(n)
			int id = lower_bound(idx.begin(), idx.end(), cy) - idx.begin();
			while (check[id] != 0) 
				id += 1;
			if (id > cls.size() - 1 || cls[id].first < cy) 
				break;
			check[id] = 1;
			cy = cls[id].second;
		}
		res += 1;
	}
	cout << res;
	return 0;
}

'책장 > 알고리즘' 카테고리의 다른 글

[백준-1918] 후위 표기식  (1) 2021.08.11
[백준-20040] 사이클 게임  (0) 2021.08.11
[프로그래머스] 큰수만들기  (0) 2021.08.11
[백준-1082] 방번호  (0) 2021.08.10
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

string solution(string number, int k) {
    string answer = "";
    int window_size = number.size() - k;

    int max_idx = -1;
    char max_val;
    for(int i=0 ; i< window_size ; i++) {
        max_val = '0';
        for(int j = max_idx + 1 ; j<i+k+1 ; j++) {
            if(max_val < number.at(j)) {
                max_val = number.at(j);
                max_idx = j;
            }
        }
        answer.push_back(max_val);
    }    
    return answer;
}

 

i번째의 수를 고를 때, k 개를 뽑아야 되니까 최소 i부터 k+1 범위에 있는 수중에서 큰 수만을 고르게 되면 결국 전체적으로 봤을 때 최대 값이 생기게 된다.

그리디한 방법 하에, answer 는 앞자리 수가 크면 클수록 가중치가 크기 때문에 앞에서부터 뽑힐 수 있는 모든 후보군 중에서 큰 값을 뽑게 되면 결국 제일 큰 값이 처리되기 때문에 답이 나온다.

 

쉽게 설명하자면 각 자리수의 수를 확인할 수 있는 윈도우를 설정하고, 해당 윈도우에서 가장 큰 값을 체킹하고 answer 에 넣어준다. 그렇게 되면 확실하게 k 개를 뺀 개수가 나오게 되고 가장 큰 값이 답으로 나오게 된다.

 

'책장 > 알고리즘' 카테고리의 다른 글

[백준-20040] 사이클 게임  (0) 2021.08.11
[백준-11000] 강의실 배정  (0) 2021.08.11
[백준-1082] 방번호  (0) 2021.08.10
[백준-15684] 사다리 조작  (0) 2021.08.09
1028번: 다이아몬드 광산

 

dp 문제였다.

dp 배열에 문자열을 넣고 drop up 방식으로 단순하게 탐색하는 식으로 문제를 풀 수 있었다.

 

로직은 다음과 같다.

dp[1] 부터 차례대로 값을 비교하되 하나의 dp 마다 n개의 수를 넣기 전의 dp 값과 비교하여 해당 수를 추가한 뒤 값이 더 큰 값을 dp 값으로 갱신하는 로직.

말이 좀 어려울 수 있겠지만 dp[i - (수의값)] 에서 해당 수를 첨가하여 커지나 안커지나만 확인하면 되는 문제였다.

dp 라 푸는데 좀 오래 걸렸지만.. 다음에는 더 잘 기억하자

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <tuple>
#include <string>
#include <string.h>
#include <stack>
#include <set>
#include <map>


using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
typedef tuple<int, int, string> tii;
string dp[200];

string convert(string a) {
	string res;
	if (a == "") return "";
	for (int i = 0; i < a.size(); i++) {
		if (a.at(i) == '0') continue;
		else {
			res = a.substr(i);
			break;
		}
	}
	if (res == "") return "0";
	else return res;
}
int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int n, input, money;
	vector<int> v;
	int _min = 50;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> input;
		v.push_back(input);
		_min = min(_min, input);
	}
	cin >> money;

	for (int i = _min; i <= money; i++) {
		for (int j = 0; j < n; j++) {
			if (i - v[j] < 0) continue;
			for (int k = 0; k < dp[i - v[j]].size(); k++) {
				if (dp[i - v[j]][k] - '0' >= j) continue;
				string new_str = convert(dp[i - v[j]].substr(0, k) + to_string(j) + dp[i - v[j]].substr(k + 1));
				// cout << new_str << endl;
				if (dp[i].size() < new_str.size()) {
					dp[i] = new_str;
				}
				else if (dp[i].size() == new_str.size()) {
					dp[i] = max(dp[i], new_str);
				}
			}
			if (dp[i - v[j]].empty() && j == 0)
				continue;

			string t = dp[i - v[j]] + to_string(j);
			if (dp[i].length() < t.length())
				dp[i] = t;
			else if (dp[i].length() == t.length())
				dp[i] = max(dp[i], t);
		}
	}

	if (dp[money].empty()) cout << 0;
	else {
		cout << dp[money];
	}
	return 0;
}

 

 

'책장 > 알고리즘' 카테고리의 다른 글

[백준-11000] 강의실 배정  (0) 2021.08.11
[프로그래머스] 큰수만들기  (0) 2021.08.11
[백준-15684] 사다리 조작  (0) 2021.08.09
[백준-2805] 나무자르기  (0) 2021.08.08

15684번: 사다리 조작

이거는 약간 구현에 신경써야 하는 문제이다.

여러번 풀어보는 것도 나쁘지 않다고 생각한다.

일단 하나씩 다 돌아보는 단순한 브루트포스 알고리즘으로 풀리는 문제이다. 다만 각 가로줄들을 데이터로 잘 표현하여 탐색을 돌리는 구현이 깔끔하게끔 짜야하는 문제가 있다.

각 가로줄을 일일이 저장하는 것이 아닌 특정 점에서 밑으로 내려올 수 있다 없다 로 가로줄을 판단할 수 있다.

예를 들어 check[3][4]=1 이라면 (3,4) 에서 사다리 밑으로 내려올 수 있다는 의미가 될 수 있다.

이를 이용해 check[x][y], check[x][y-1], check[x][y+1] 이 3개의 값을 비교하여 1인 경우 dfs 탐색을 돌리는 식으로 로직을 구성하면 문제를 풀 수 있다.

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>

using namespace std;

int n, m, h; 
int arr[100][100];
int check[100][100];
int _min = 1000000;
bool isok() {
    int cx;
    for (int i = 1; i <= n; i++) {
        cx = i;
        for (int j = 1; j <= h; j++) {
            if (check[j][cx] == 1) cx = cx + 1;
            else if (check[j][cx-1] == 1) cx = cx - 1;
        }
        if (cx != i) return false;
    }
    return true;
}
void dfs(int x, int cnt) {
    if (cnt >= 4) {
        return;
    }
    if (isok()) {
        _min = min(_min, cnt);
        return;
    }

    for (int i = x; i <= h; i++) {
        for (int j = 1; j < n; j++) {
            // check[x][y] = 1 
            if (check[i][j] == 1) continue;
            if (check[i][j-1] == 1) continue;
            if (check[i][j+1] == 1) continue;

            check[i][j] = 1;
            dfs(i, cnt + 1);
            check[i][j] = 0;
        }
    }
}
int main() {
    int a, b;

    cin >> n >> m >> h;
    for (int i = 0; i < m; i++) {
        cin >> a >> b;
        check[a][b] = 1;
    }
    dfs(1, 0); 
    if (_min == 1000000) cout << -1;
    else cout << _min;

    return 0;
}

'책장 > 알고리즘' 카테고리의 다른 글

[프로그래머스] 큰수만들기  (0) 2021.08.11
[백준-1082] 방번호  (0) 2021.08.10
[백준-2805] 나무자르기  (0) 2021.08.08
[백준-2517] 달리기  (0) 2021.08.08

2805번: 나무 자르기

간단한 이분탐색 문제이다.

최대값을 right로 0을 left 로 설정한 후 calc 함수만 짠뒤 간단히 이분탐색을 돌리면 풀리는 간단한 문제이다.

다만 값의 범위가 int 값의 범위를 넘어가기 때문에 이를 long long 으로 변환해주어 적절하게 풀면 간단히 풀린다.

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <cstdio>
#include <tuple>
#include <string>
#include <string.h>
#include <stack>
#include <set>


using namespace std;
typedef pair<int, int> pii;
typedef tuple<int, int, string> tii;
typedef long long LL;

LL n, m;
LL arr[1000001];

LL calc(LL x) {
    LL res = 0;
    for (int i = 0; i < n; i++) {
        res += (arr[i] - x > 0) ? arr[i] - x : 0;
    }
    return res;
}
int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    cin >> n >> m;

    LL right = 1000000000;
    LL left= 0;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }

    LL mid;
    LL ans = 0;
    while (left <= right) {
        mid = (left + right) / 2;
        LL res = calc(mid);
        if (res >= m) {
            ans = max(ans, mid);
            left = mid + 1;
        }
        else if(res < m){
            right = mid - 1;
        }
    }

    cout << ans;

    return 0;
}

'책장 > 알고리즘' 카테고리의 다른 글

[백준-1082] 방번호  (0) 2021.08.10
[백준-15684] 사다리 조작  (0) 2021.08.09
[백준-2517] 달리기  (0) 2021.08.08
[백준-1202] 보석도둑  (0) 2021.07.13

[https://www.acmicpc.net/problem/2517]

시간 복잡도가 매우 중요한 문제이다. 일반적인 생각으로 매 선수가 들어올 때마다 앞에서 몇번째인지 찾기 위해서는 "기존 입력을 받는 n" * "자기보다 작은 선수들 찾기 n" 가 되어 n^2 으로 시간이 터져버린다.

때문에 이 문제를 풀기 위해서는 최소한 nlogn 의 로직으로 풀어야 한다. 즉, 이분탐색이나 logn 복잡도의 탐색 알고리즘이 필요하다. 이를 위해 세그먼트 트리를 사용할 수 있다.

로직은 다음과 같다.

  1. 첫번째 선수 하나 들어왔을 때 세그먼트 트리에 체크해준다.
  2. 두 번째 선수 부터는 세그먼트 트리에 업데이트 하기전 자기보다 작은 값까지 몇까지 있는지 logn의 복잡도로 확인한다.
  3. 위 1, 2 번 방법을 반복한다.

즉, 세그먼트 트리에는 해당 값이 있는지 1로 체크만 해주고, 이에 따라 부분 합의 의미는 해당 범위까지 선수가 몇명있는지의 의미이다. 이를 통해 logn의 복잡도로 탐색할 수 있으며, 결국 nlogn 의 복잡도로 문제를 풀 수 있게 된다.

  • 코드
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <vector> 
#include <algorithm> 
#include <queue> 
#include <cstdio>
#include <tuple> 
#include <string> 
#include <string.h> 
#include <stack> 
#include <set>

using namespace std;
typedef long long LL;
typedef pair<LL, LL> pii;
typedef tuple<int, int, string> tii;


LL n, m;
LL a, b;
vector<pii> v;
int tree[2000000];

int query(int node, int s, int e, int l, int r) {
    if (l > e || r < s) return 0;
    if (l <= s && e <= r) return tree[node];
    else {
        return query(node * 2, s, (s+e)/2, l, r) + query(node * 2 + 1, (s + e) / 2+1, e, l, r);
    }
}

void update(int node, int s, int e, int idx, int v) {
    if (idx < s || e < idx) return;
    if (s == e) tree[node] = v;
    else {
        update(node * 2, s, (s + e) / 2, idx, v);
        update(node * 2 + 1, (s + e) / 2 + 1, e, idx, v);
        tree[node] = tree[node * 2] + tree[node * 2 + 1];
    }
}

bool compare(pii a, pii b) {
    return a.second  < b.second;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    cin >> n;
    for (LL i = 0; i < n; i++) {
        cin >> a;
        v.push_back({ i,a });
    }

    int res = 0;
    sort(v.begin(), v.end(), compare);
    for (int i = 0; i < v.size(); i++) {
        v[i].second = ++res;
    }

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

    for (int i = 0; i < v.size(); i++) {
        int cnt = 0;
        int cur_power = v[i].second;

        if (cur_power > 1) {
            // 제일 작은 원소가 아니라면
            cnt = query(1, 1, res, 1, cur_power - 1);
        }
        update(1, 1, res, cur_power, 1);
        printf("%d\n", i + 1 - cnt);
    }


    return 0;
}
```

'책장 > 알고리즘' 카테고리의 다른 글

[백준-15684] 사다리 조작  (0) 2021.08.09
[백준-2805] 나무자르기  (0) 2021.08.08
[백준-1202] 보석도둑  (0) 2021.07.13
[프로그래머스]타겟넘버  (0) 2021.04.30

https://www.acmicpc.net/problem/1202

 

1202번: 보석 도둑

첫째 줄에 N과 K가 주어진다. (1 ≤ N, K ≤ 300,000) 다음 N개 줄에는 각 보석의 정보 Mi와 Vi가 주어진다. (0 ≤ Mi, Vi ≤ 1,000,000) 다음 K개 줄에는 가방에 담을 수 있는 최대 무게 Ci가 주어진다. (1 ≤ Ci

www.acmicpc.net

위 문제의 경우 1개의 우선순위큐를 통해 구할 수 있다. 이런 그리디 문제가 나는 비슷한 문제가 많으니 기억해두자.

일단 입력 받은 가방과 보석을 모두 오름차순으로 정렬 한다.

그 후 작은 가방 하나씩 꺼내고, 보석 배열을 순회하면서 집어넣을 수 있는 보석을 우선순위 큐에 집어 넣는다. (by while) 다 집어 넣었으면 우선순위 큐의 top 에 위치하는 보석을 해당 가방에 넣는 것으로 확정하고 그 다음 가방을 똑같은 방법으로 확인하는 로직이다. 말이 쉽지 직접 구현하는 건 좀 어렵다.

 

#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>

using namespace std;
typedef long long LL;
typedef pair<LL, LL> pii;
int n, k;

int main()
{
    ios_base::sync_with_stdio(0);

    cin.tie(0); //cin 실행속도 향상

    scanf("%d %d", &n, &k);
    vector<pii> jewl;
    vector<LL> bag;
    priority_queue<LL> pq;

    LL m, v, t;
    for (int i = 0; i < n; i++)
    {
        cin >> m >> v;
        jewl.push_back({m, v});
    }

    for (int i = 0; i < k; i++)
    {
        cin >> t;
        bag.push_back(t);
    }
    sort(bag.begin(), bag.end());
    sort(jewl.begin(), jewl.end());

    long long answer = 0;
    int idx = 0;
    for (int i = 0; i < k; i++)
    {
        while (idx < n && jewl[idx].first <= bag[i])
        {
            // 가방에 넣을 수 있는지, 없는지 확인
            pq.push(jewl[idx++].second);
        }
        if (!pq.empty())
        {
        	// 우선순위 큐의 top에 있는 놈이 정답
            answer += pq.top();
            pq.pop();
        }
    }

    cout << answer;

    return 0;
}

'책장 > 알고리즘' 카테고리의 다른 글

[백준-2805] 나무자르기  (0) 2021.08.08
[백준-2517] 달리기  (0) 2021.08.08
[프로그래머스]타겟넘버  (0) 2021.04.30
완전탐색  (0) 2021.04.24

타겟넘버

 

이 문제는 dfs 로 풀었을 때보다 bfs 로 풀었을 때 시간 복잡도가 적게 나오는 문제이다.

출처 https://namu.wiki/w/BFS

해당 그림을 봤을 때는 속도가 비슷해 보이지만

기본적으로 재귀 함수 틀의 DFS 는 BFS 보다 검색 속도가 느리기 때문에 동일 방법을 사용해도 될 때 시간 단축을 위해서는 BFS 를 사용하는 것이 좋다.

 

#include <string>
#include <vector>
#include <iostream>
#include <queue>
#include <tuple>

using namespace std;
int _count = 0;
void dfs(vector<int> check, vector<int> num, int target, int current, int idx, int _size) {
    if (target == current && _size == num.size()) {
        _count += 1;
        return;
    }

    for (int i = idx; i < num.size(); i++) {
        if (check[i] == 0) {
            check[i] = 1;
            dfs(check, num, target, current + num[i], i+1, _size + 1);
            dfs(check, num, target, current - num[i], i+1, _size + 1);
            check[i] = 0;
        }
    }
}

void bfs(vector<int> check, vector<int> num, int target) {
    queue<tuple<int, int, int>> q; // idx, neg
    q.push(make_tuple(0, 1, num[0]*1));
    q.push(make_tuple(0, -1, num[0] * (-1)));
    check[0] = 1;

    int idx, neg;
    int sum = 0;


    while (!q.empty()) {
        
        idx = get<0>(q.front());
        neg = get<1>(q.front());
        sum = get<2>(q.front());
        q.pop();

        if (target == sum && idx == num.size() - 1) {
            _count += 1;
            continue;
        }
        else if (idx >= num.size() -1) {
            continue;
        }
        else {
            q.push(make_tuple(idx + 1, 1, sum + num[idx + 1] * 1));
            q.push(make_tuple(idx + 1, -1, sum + num[idx + 1] * (-1)));
            continue;
        }
    }
}
int solution(vector<int> numbers, int target) {
    int answer = 0;
    vector<int> check(numbers.size(), 0);
    // dfs(check, numbers, target, 0, 0, 0);
    bfs(check, numbers, target);
    cout << _count << endl;
    return _count;
}

 

'책장 > 알고리즘' 카테고리의 다른 글

[백준-2517] 달리기  (0) 2021.08.08
[백준-1202] 보석도둑  (0) 2021.07.13
완전탐색  (0) 2021.04.24
깊이/너비우선탐색(DFS,BFS)  (0) 2021.04.23

완전탐색

TERAJOO
|2021. 4. 24. 00:05

 

재귀적 호출에 대한 개념을 먼저 잡고 넘어가자. 그 이유는 완전 탐색 시에, 해당 호출 방식을 자주 활용하기 때문이다.

 

재귀함수의 기본적인 이해

  • 재귀함수란: 함수 내에서 자기 자신을 다시 호출하는 함수
  • : 자신이 수행할 작업을 유사한 형태의 여러 조각으로 쪼갠 뒤 그 중 한 조각을 수행하고, 나머지를 자기 자신을 호출해 실행하는 함수
  • 재귀함수 호출방식
    void RecursiveFunction(void) {     
    	printf("Recursive function example1 \n");     
        RecursiveFunction(); 
    }

※ 기저 사례 (base case)

▷ 더 이상 쪼개지지 않는 가장 작은 작업, 즉 최소한의 작업에 도달했을 때 답을 곧장 반환하는 조건문에 포함될 내용
▷ 기저 사례를 선택할 때는 존재하는 모든 입력이 항상 기저 사례의 답을 이용해 계산될 수 있도록 신경써야 한다.

 


완전탐색(exhaustive search)

 

① 완전탐색이란?

▷ '무식하게 푼다(brute-force)'는 컴퓨터의 빠른 계산 능력을 이용해 가능한 경우의 수를 일일이 나열하면서 답을 찾는 방법을 의미. 이렇게

가능한 방법을 전부 만들어 보는 알고리즘

을 뜻한다.
▷ 완전 탐색은 컴퓨터의 빠른 계산 속도를 잘 이용하는 방법이다.
▷ (예제) n개의 원소 중 m개를 고르는 모든 조합을 출력하는 코드

// n : 전체 원수의 수 
// picked : 지금까지 고른 원소들의 번호 
// toPick : 더 고를 원소의 수
// 일 때, 앞으로 toPick개의 원소를 고르는 모든 방법을 출력한다. 

void pick(int n, vector<int>& picked, int toPick) {   
	// 기저 사례 : 더 고를 원소가 없을 때 고른 원소들을 출력한다.   
	if(toPick == 0) {
    	printPicked(picked); 
        return; 
    }   
    
    // 고를 수 있는 가장 작은 번호를 계산한다.   
    int smallest = picked.empty() ? 0 : picked.back() + 1;   

    // 이 단계에서 원소 하나를 고른다.   
    for(int next = smallest; next < n; ++next) {
        picked.push_back(next);     
        pick(n, picked, toPick - 1);     
        picked.pop_back();   
    } 
 }

 

② 완전 탐색 방법

  • Brute Force for문과 if문을 이용하여 처음부터 끝까지 탐색하는 방법
  • 비트마스크: 거두절미 하고 이야기 하면, 비트마스크는 알고리즘 이라기 보단 테크닉에 가깝습니다. 비트는 컴퓨터에서 다루는 최소 단위이며, 정수를 이진수로 표현, 비트 연산을 통해 문제를 해결해 나가는 기술을 비트마스크 라고 한다.
  • 순열 순열의 시간 복잡도는 O(N!)으로 백트래킹과 비슷하다. check 배열 하나 두고 들어갔다가 나오고, 들어갔다가 나오는 방식을 구현한다. 예시는 밑에 추가해놨으니 참고해보자.
  • 백트래킹(DFS): 백트래킹은 모든 조합의 수를 살펴보는 것인데 단 조건이 만족할 때 만이다. 모든 경우의 수를 모두 찾는 것보다 ‘경우에 따라' 훨씬 빠를 수 있다. 왜냐하면 조건이 만족하는 경우라는 조건이 있기 때문이다.한 컬럼에 Queen을 두었으면 오른쪽 컬럼에 가능한 자리에 Queen을 둔다. 또 다시 오른쪽 컬럼 중에서 가능한 자리에 Queen을 둔다. 이 과정을 반복한다. 만약에 N개의 Queen을 두었다면 카운트를 증가 시킨다. 이제부터가 중요한데, 성공하기 직전으로 상태를 되돌린다(back). 그리고 N번째 컬럼 중 다른 칸들을 시도한다. 만약에 없다면 N-1번째 컬럼에 있는 Queen을 들어서 다음으로 가능한 칸으로 옮긴다. 다시 N번째 컬럼에 Queen을 둘 수 있는 곳을 찾는다. N-1번째 컬럼의 모든 경우를 시도했다면, N-2번째로 되돌아가 다음으로 가능한 위치로 Queen을 옮긴다.
  • 구체적인 예를 들어보자. 정렬되어 있지 않은 숫자 리스트 중에 특정 숫자를 찾는 것은 n을 리스트 길이라고 했을 때, O(n) 시간 복잡도를 갖는다. 이 경우에는 백트래킹은 의미가 없다. 한 개씩 모두 살펴볼 수 밖에 없기 때문이다. 반면 N-Queen 문제의 경우는 다르다.

#include<iostream> 
#include<vector>   
#define MAX 5 
using namespace std;   

char Arr[MAX]; 
bool Check[MAX]; 
vector<char> V;
void Permutation(int n, int r) 
{     
	if (n == r) {
    	for (int i = 0; i < V.size(); i++) {
        	cout << V[i] << " ";
        }
        cout << endl;
        return;
    }
    for (int i = 0; i < MAX; i++){
    	if (Check[i] == true) continue;
        Check[i] = true;
        V.push_back(Arr[i]); 
        Permutation(n+1 , r);
        V.pop_back();
        Check[i] = false;
    }
}

int main(void) {
	Arr[0] = 'a';
    Arr[1] = 'b';
    Arr[2] = 'c';
    Arr[3] = 'd';
    Arr[4] = 'e';
    Permutation(0,2);
}
#include<iostream> 
#include<vector>   
#define MAX 5 
using namespace std;
char Arr[MAX]; 
bool Check[MAX];
vector<char> V;  
void combination(int idx, int n, int r) {
  if (n == r){
    for (int i = 0; i < MAX; i++){
      if (Check[i] == true){
        cout << Arr[i] << " "; 		        
      } 	    
   	} 	    
  	cout << endl;
  	return;     
  }
  for (int i = idx; i < MAX; i++) {
  	if (Check[i] == true) continue;
    Check[i] = true;          
    combination(i,n+1,r);         
    Check[i] = false;     
  } 
}

int main(void) {
	Arr[0] = 'a';
   	Arr[1] = 'b'; 
    Arr[2] = 'c'; 
    Arr[3] = 'd'; 
    Arr[4] = 'e';
    combination(0,0,2);
}

 

위의 5가지 방법을 이용한 완전탐색 방법 모두를 익히는 것이 좋다.

모든 경우의 수를 탐색하는 방법은 문제를 접했을 때 가장 쉬운 방법이지만, 기초라고도 볼 수 있다. 그리고 문제에서 제한조건과 위의 몇 가지 SKILL을 추가하여 푼다면 문제 해결 시간을 크게 향상 시킬 수 있다. 참고로 완전탐색을 완벽하게 하기 위해서는 DFS, BFS 등등 코드를 두루두루 알고 있어야 한다.

 

'책장 > 알고리즘' 카테고리의 다른 글

[백준-1202] 보석도둑  (0) 2021.07.13
[프로그래머스]타겟넘버  (0) 2021.04.30
깊이/너비우선탐색(DFS,BFS)  (0) 2021.04.23
  (0) 2021.04.23