블로그 글이니 당연하겠지만, 이 글은 개인적인 견해가 담겨있는 글입니다.

 

 

신입사원 공채, 경력사원 공채에서 가장 많이 쓰이는 언어는 C/C++과 Java이며, 요즘에는 Python도 지원하는 경우가 꽤 늘고 있습니다.

 

 

솔직히 어떤 언어를 사용해야 할 지는 정답은 없지만, 각자에게 해답은 있을 수 있습니다.

 

일단 고려해야하는 부분들이 다양하게 있습니다.

 

언어 선택의 기준

본인이 익숙하거나 잘하는 언어

본인이 이미 어느정도 능숙하게 사용하거나, 익숙하게 쓸 수 있는 언어가 있다면 해당 언어를 사용하는 것을 고려해보면 좋습니다.

딱히 능숙한 언어가 없고, 다 고만고만하다면 다른 기준으로 언어를 골라서 조금 더 익숙해지게 연습하는 것도 좋습니다.

만약 본인이 익숙한 언어가 C/C++, Java, Python 중 하나라면 그 언어를 골라서 쭉 공부하는게 좋을 것 같습니다.

 

많이 지원하는 언어

여기서 지원한다는 것은 원서 지원(apply)가 아닌, 언어 지원(support)라고 볼 수 있습니다. 본인이 만약 A사에 지원하려고 하고 주력언어가 OCaml인데, 해당 회사 코딩테스트 플랫폼에서 OCaml을 지원하지 않는다면 어떻게 될 까요?

많이 지원하는 언어들은 이러한 부분을 고려할 필요가 덜합니다.

 

일반적으로 C/C++및 Java를 코딩테스트 언어로 지원하지 않는 경우는 거의 없다고 보시면 됩니다.(특수한 직종이 아닌 이상)

그리고 Python의 경우 회사에 따라서 지원할 수 도, 아닐 수도 있습니다.

 

그리고 일부 회사는 꽤나 많은 언어들을 지원하는데, Javascript와 같은 언어도 지원하는 경우도 간혹 있습니다.

 

공부할 자료가 많은 언어

코딩테스트 공부를 하기 위해서는, 문제 풀이 코드들을 보면서 공부를 할 일들이 있을 것인데, 본인의 주력 언어가 C#인데 대부분의 알고리즘 문제 풀이 코드들이 C++나 Java이고 C++과 Java를 읽지 못한다면 공부하는데 있어서 애로사항이 있을 수 있습니다.

문제풀이 코드는 C/C++언어 코드가 제일 많고, 그 다음으로는 Java가 많다고 볼 수 있습니다. 이러한 점에서 C/C++이나 Java를 선택하면 남의 코드를 참고할때 유리한 점이 있습니다.

 

물론 C/C++, Java를 잘 쓰진 못하더라도 읽기라도 잘 한다면 알고리즘 공부에 크게 어렵지는 않을 것입니다.

 

 

결론

본인이 이미 익숙한 언어가 있다면 그 언어로 해라.

(Java를 잘하면 그냥 Java 하면 된다)

(Python을 잘하면, 원하는 회사에서 지원하면 파이썬 쓰고 아니면 C++해라)

 

본인이 원하는 회사에서, 본인의 주력 언어로 코딩테스트를 볼 수 없다면, C/C++, Java, Python 중에서 제일 편한거를 하나 골라라.

 

애매하면 그냥 C/C++을 주력으로 공부하라.

C/C++를 주력언어로 하면 손해 볼 일은 없다.

 

코딩테스트 수준이 아닌 대회 준비할 거면 그냥 C++해라.

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

 

2539번: 모자이크

수찬이는 선생님을 도와서 교실 벽면을 장식할 모자이크 그림을 그리기로 하였다. 이를 위하여 직사각형 모양의 큰 도화지를 준비하여 교실 벽에 붙이고 1cm 간격으로 가로선과 세로선을 그려서 정사각형 모양의 칸을 만들고, 각 칸마다 같은 색의 물감으로 색칠을 하였다. 그런데 잘못 칠해진 칸이 있음을 발견하게 되었다. 수찬이는 도화지와 색깔이 같은 색종이를 사서 잘못 칠해진 칸에 색종이를 붙이고 다시 그리는 것이 좋겠다고 생각하고 선생님께 상의를 드렸다. 선생

www.acmicpc.net

한국정보올림피아드 2007년도 초등부 3번문제이자 백준 2539번 문제 "모자이크"문제 풀이입니다.

 

2007년도때 초등부 문제는 3문제만 나왔네요. 마지막 문제 3번 문제입니다.

 

문제 분석

2차원 행렬칸에 구멍이 난 녀석이 있고, 정사각형 색종이로 이들을 모두 가려야 합니다.

 

정사각형 색종이의 개수는 정해져 있고, 겹쳐서 덮어도 됩니다. 정사각형 색종이의 최소 크기를 구하는 문제입니다.

 

뭔가 나름 어려운 문제 일 수도 있는데, 한 가지 조건 덕에 난이도가 꽤 낮아진 감이 있습니다.

 

바로 "모든 색종이는 반드시 도화지의 밑변에 맞추어 붙인다." 라는 조건이죠. 밑 변에 맞추어서 붙여야 한다는 조건이 있습니다.

 

 

행과 열 개수는 100만 미만이라고 하니, 이들을 모두 2차원 배열로 잡기는 조금 무리가 있어 보입니다.

 

알고리즘 구상

일단 모든 색종이는 도화지의 밑변에 맞추어 붙어야 한다는 조건 때문에, 구멍이 난녀석의 y좌표가 가장 높은 녀석을 커버할 수 있어야 합니다.

 

따라서 색종이의 크기 최소값은, 가장 큰 y좌표 값 보다는 같거나 커야 합니다.

 

그리고 모든 점을 덮을 최소 색종이 크기를 알아내야하는데, 이는 파라메트릭 서치로 풀 수 있습니다.

 

문제를 다음과 같이 환원합니다.

 

//(f(x)//) => 색종이 크기가 x일 때 주어진 개수의 색종이로 모든 점을 덮을 수 있는가?

 

만약 크기가 x일때 모두 덮을 수 있다면, 당연히 x+1의 크기로도 덮을 수 있겠지요.

 

만약 y크기로 덮을 수 없다면, y-1의 크기로도 덮을 수 없습니다.

 

이러한 성질을 이용하여 파라메트릭 서치(바이너리 서치)로 풀 수 있습니다.

 

 

//(f(x)//)는 그리디하게 구할 수 있습니다.

 

구멍의 x좌표들만 모아놓은 상태에서, 처음 만나는 점을 색종이의 가장 왼쪽 변에 맞게 놓는다고 치고,

 

지금 놓여진 색종이의 오른쪽 변을 넘어가면 새로운 색종이를 덮는 식으로, 총 색종이가 몇 장 필요한지 알아내서

 

주어진 색종이 개수로 덮을 수 있는지를 확인하면 됩니다.

 

따라서 //(f(x)//)는 //(O(N)//)만에 구할 수 있고, 여기서 //(N//)는 잘못 칠해진 색종이 칸의 개수이므로 1000이 됩니다.

계산량은 총 //(1000 * lg1000000//)이므로, 쉽게 구할 수 있습니다.

 

코드

더보기
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
vector<int> arr;
int need(int width) {
	// 색종이 폭이 width일때, 다 덮기 위해 필요한 종이의 수
	int prev = -1;
	int ret = 0;
	for (int pos : arr) {
		if (prev == -1) {
			//처음 종이를 놓는 경우, [prev, prev+width) 까지 커버 가능
			prev = pos;
			ret++;
		}
		else if (prev + width <= pos) {
			prev = pos;
			ret++;
		}
	}
	return ret;
}
int main() {
	int row, col, num_paper, num_hole;
	int max_height = 0;
	cin >> row >> col >> num_paper >> num_hole;
	for (int i = 0; i < num_hole; i++) {
		int x, y; //행, 열
		cin >> x >> y;
		max_height = max(max_height, x);
		arr.push_back(y);
	}
	sort(arr.begin(), arr.end());
	int l = max_height;
	int r = 1000000; //range = [l, r]
	while (l < r) {
		int m = (l + r) / 2;
		if (need(m) <= num_paper) {
			//가능한 경우
			r = m;
		}
		else {
			l = m + 1;
		}
	}
	cout << l << endl;
	return 0;
}

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

 

1713번: 후보 추천하기

첫째 줄에는 사진틀의 개수 N이 주어진다. (1≤N≤20) 둘째 줄에는 전체 학생의 총 추천 횟수가 주어지고, 셋째 줄에는 추천받은 학생을 나타내는 번호가 빈 칸을 사이에 두고 추천받은 순서대로 주어진다. 총 추천 횟수는 1,000번 이하이며 학생을 나타내는 번호는 1부터 100까지의 자연수이다.

www.acmicpc.net

한국정보올림피아드 초등부 2007년도 전국본선 1번문제이자 백준 1713번 문제 "후보 추천하기" 문제 풀이입니다.

 

문제 개요

정올 초등부 1번문제 치고는 solved.ac 티어가 골드5 로 높은 편입니다.

문제 내용을 보면, 약간 특이한 룰을 따라서 코딩을 해야 합니다. 시뮬레이션 종류의 문제로 볼 수 있습니다.

 

LRU Cache 구현하는 것 같은 느낌이고, 사진 틀에 학생 사진을 넣고 빼는 것을 잘 시뮬레이션 해야 합니다.

 

특히나 중요한 부분은 사진 틀이 꽉 찼을 때 어떤 녀석을 빼느냐에 관한 문제인 것 같은데, 사진 틀의 개수가 최대 20개 밖에 되지 않으니, 뺄 녀석을 그때 그때 모든 사진 틀을 다 순회하면서 체크하면 될 것 같습니다.

 

총 추천회수는 1000이니, 최악의 경우 계산량은 //(1000*20//)으로 2만 정도가 됩니다.

시간복잡도는 크게 문제가 되지는 않을 것 같습니다.

 

사진 틀에서 삭제되는 경우, 추천 수가 0이 되는 것은 사진틀에서 뺀 경우 추천 수를 트래킹 하지 않으면 되므로 구현을 더 간단하게 해 줍니다.

 

구현전략


사진틀에서 가장 추천 수가 적거나, 추천수가 같다면 더 오래된 녀석을 빼야 한다." 이 조건을 쉽게 하도록 구현을 해야하는데, pair<int, int>를 사용하면 편할 듯 합니다.

 

C++ STL에 있는 pair은 비교 연산자가 기본적으로 first끼리 비교하고, first가 같다면 second끼리 비교하는 탬플릿 구조체입니다.

template class<T1, T2>
class pair {
	T1 first;
    T2 second;
    bool operator<(const class pair& rhs) const {
    	if (first != rhs.first) {
        	return first < rhs.first;
        }
        return second < rhs.second;
    };
    bool operator>(const class pair& rhs) const {
    	if (first != rhs.first) {
        	return first > rhs.first;
        }
        return second > rhs.second;
    }
};

대략 위와 같은 형태라고 보시면 됩니다.

 

따라서 first에 투표 수, second에 추가한 시기를 넣으면, pair값이 최소인녀석을 제거하면 되는 형식입니다.

 

코드

더보기

 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
map<int, pii> slot; //pii.first = vote , pii.second = age

int main() {
	int n, k;
	int cnt = 0;
	cin >> n >> k;
	for (int i = 1; i <= k; i++) {
		int tmp;
		cin >> tmp;
		(void)0;
		if (slot.find(tmp) == slot.end()) {
			//기존에 없는 경우
			if (slot.size() < n) {
				slot[tmp].first = 1;
				slot[tmp].second = i;
			}
			else {
				//기존거 지우고
				int victim = slot.begin()->first;
				pii value = slot.begin()->second;
				for (auto a : slot) {
					if (a.second < value) {
						value = a.second;
						victim = a.first;
					}
				}
				slot.erase(victim);
				slot[tmp].first = 1;
				slot[tmp].second = i;
			}
		}
		else {
			//있는 경우 투표만 추가시킨다.
			slot[tmp].first = slot[tmp].first + 1;
		}
	}
	for (auto it : slot) {
		printf("%d ", it.first);
	}
	return 0;
}

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

 

6988번: 타일 밟기

첫째 줄에는 타일의 개수 N이 주어진다. N은 3 이상 3,000 이하이다. 둘째 줄에는 N개의 타일에 적힌 자연수들이 증가하는 순서로 빈칸을 사이에 두고 주어진다. 타일에 적힌 자연수들은 각각 1,000,000 이하이다.

www.acmicpc.net

백준 6988번 문제이자, 정보올림피아드 초등부 2008년도 전국본선 3번문제 타일 밟기 문제이다.

 

문제 분석

타일이 최대 3000개가 주어지고, 타일 번호는 정렬되어 있다.

 

최소 3개 이상 연속된 타일의 숫자 합 중 최대값을 구해야 하고, 연속된 타일이라 함은

타일들 간에 값의 차이가 일정해야 한다는 것이다.

 

등차수열을 이루는 타일 값들의 합이 최대여야 한다는 것

 

타일에 적힌 수는 100만까지 나올 수 있다.

 

알고리즘 설계 및 시간복잡도 분석

타일에 적힌 수는 자연수이므로 1부터 100만의 범위이다.

그러면 타일 간 값의 차이는 1부터 50만 정도가 된다고 볼 수 있다 대충

1 50만 100만1 이렇게 하면 3개가 된다.

 

즉 1 500000 1000001 이렇게 인데, 사실 100만1은 안나오지만, 대충 타일 값 차이가 50만 가까이가

최대값이라고 보면 된다.

 

따라서 타일간 차이값인 diff값을 모두 다 찾아보면서 순회하려면 diff값이 50만개가 될 것이고

 

diff값 별 타일 모두를 다 훑어야 하므로 for-loop 도는 횟수는 50만 * 3천 정도 인데

 

이 값은 15억 정도 되므로, 15초 이상 걸리는 알고리즘 이라고 볼 수있다. 고로 Time Limit Exceed가

자명한 알고리즘이라는 뜻

 

그러면 이 계산량을 줄여야 하는데, N값이 3000이므로 꽤나 작다.

 

3000이라면 //(O(N^2)//)알고리즘을 짜도 돌아간다는 뜻.

 

 

여기서 잘 모르겠어서 아래의 알고리즘 분류를 "보기"를 눌러서 확인해 보았는데

다이나믹 프로그래밍이라고 힌트를 얻었다.

 

그러면 N값을 기준으로 메모이제이션 배열을 활용하면 될 것 같은데, N크기에 맞는 2차원 디피까지는 가능할 것 같다.

 

그렇다면 dp 배열 인덱스에 diff값을 넣을 수는 없다. 메모리 문제도 있고 시간 문제도 있고

 

그래서 //(dp[3000][3000]//)식으로 잡고, //(dp[i][j]//)라 했을 때, //(i//)가 마지막 인덱스고 //(j//)가 직전 인덱스로 해서

 

//(arr[i] - arr[j]//)로 diff값을 구할 수 있으니, 이를 활용해 보기로 했다.

 

dp[i][j] = i번째에서 끝나고, 그 직전에 arr[j]의 값을 갖는 시퀀스 합의 최대값

이라고 정의해보자.

 

처음 3개짜리만 대상으로 dp배열을 모두 전처리 해 놓은 뒤,

모든 dp배열을 돌면서, arr[j], arr[i]를 통해서 다음에 나올 수 있는 값을 구해서, 그 값이 존재하는 지

확인한 뒤, 존재한다면 그 인덱스를 //(k//)라 하고, //(dp[k][i]//)에 //(dp[i][j] + arr[k]//)를 넣는 식으로 구하면 될 듯하다.

 

값이 존재하는지는, 값의 범위가 1부터 100만이므로 배열을 잡기 보다는 c++ stl map같은 자료구조를 쓰면 좋을 것 같다.

배열로 잡는다면 값 자체는 3000개라서 sparse한 배열이 될 것이고, 메모리가 터질 수 있으므로

 

구현 코드

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
int arr[3005];
map<int, int> mm;
ll dp[3005][3005]; // dp[i][j] => i번째에서 끝나고, 이전이 j인 시퀀스의 최대 누적합
int main() {
	ll ans = 0;
	int n;
	cin >> n;
	for (int i = 0; i < n; i++) {
		int tmp;
		cin >> tmp;
		arr[i] = tmp;
		mm[tmp] = i;
	}
	for (int i = 1; i < n; i++) {
		int cur = arr[i];
		for (int j = 0; j < i; j++) {
			int prev = arr[j];
			ll next = cur + (cur - prev);
			if (mm.find(next) != mm.end()) {
				int next_idx = mm[next];
				dp[next_idx][i] = cur + prev + next;
				ans = max(ans, dp[next_idx][i]);
			}
		}
	}
	for (int i = 1; i < n; i++) {
		int cur = arr[i];
		for (int j = 0; j < i; j++) {
			if (dp[i][j]) {
				int prev = arr[j];
				ll next = cur + cur - prev;
				if (mm.find(next) != mm.end()) {
					int next_idx = mm[next];
					dp[next_idx][i] = dp[i][j] + next;
					ans = max(ans, dp[next_idx][i]);
				}
			}
		}
	}
	cout << ans << endl;

	return 0;
}

 

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

 

6986번: 절사평균

첫째 줄에 절사평균(N, K)를, 둘째 줄에 보정평균(N, K)를 각각 소수점이하 셋째 자리에서 반올림하여 둘째 자리까지 출력한다. 예를 들어 결과값이 9.667인 경우 9.67로, 5인 경우 5.00으로, 5.5인 경우에는 5.50으로 출력한다.

www.acmicpc.net

문제 내용

 

2008년도 초등부 문제 풀어보다가 1번 문제라서 쉽겠거니 하고 했다가 우수수 하고 틀려버렸다 ㅡㅡ

 

문제 내용은 크게 어려운 것은 없다.

 

규칙에 맞게 절사평균과 보정 평균을 구해주면 되는데, 배열로 받은 뒤 소팅해서

 

앞에 k개 만큼을 날리던지, 근처 값으로 변경해주던지 하면 된다.

 

 

하 그놈의 Floating Point 정확도 문제

 

Floating Point precision 문제때문에 하....

 

 

일단 입력값 중 점수는 0 ~ 10범위의 실수에 소숫점 한자리까지 나오는 floating point이고

전채 개수가 10만개 까지 나올 수 있다.

 

10만개라는 이유 때문에 precision 문제가 발생할 수 있다.

컴퓨터에서 사용하는 부동소수점 방식은 작은 에러값이 발생할 수 있다.

따라서 이게 누적되면 정확하지 않은 값이 나올 수 있는데

 

 

따라서 정수로 환원해서 풀어야 한다.

 

 

결과값은 소수점 2자리까지 출력해야하고, 이로 인해서 100을 곱하면 되겠지만, 소수점 셋째자리에서

 

반올림을 해야 하므로 1000을 곱해서 소수점 셋째자리까지 일단 표현 가능하게 정수로 받았다.

 

그리고 마지막에 5를 더해서 반올림을 구현했다.

 

출력할때는 1000을 나눈 정수 부분과 1000로 모듈러 연산을 한 소수부분(fraction)을 따로 출력해주었다.

 

5가 정답일 때 5.00라고 출력해야 하므로 포맷 스트링에 0 extension도 해 주어야 한다.

 

%02d와 같이 하면 너비 2만큼 0을 꽉채워서 출력해준다.

 

구현 코드

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
int main() {
	int n, k;
	vector<ll> arr;
	cin >> n >> k;
	arr.resize(n);
	for (int i = 0; i < n; i++) {
		double tmp;
		cin >> tmp;
		arr[i] = (ll)(tmp * 1000);
	}
	sort(arr.begin(), arr.end());
	ll s = 0;
	for (int i = k; i + k < arr.size(); i++) {
		s += arr[i];
	}
	ll ans = s / (arr.size() - k - k) + 5;
	printf("%lld.%02lld\n", ans / 1000, ans % 1000 / 10);
	s = 0;

	s += k * arr[k];
	for (int i = k; i + k < arr.size(); i++) {
		s += arr[i];
	}
	s += k * arr[arr.size() - 1 - k];
	ans = s / arr.size() + 5;
	printf("%lld.%02lld\n", ans / 1000, ans % 1000 / 10);
}

 

오랜만에 퇴근 후 23:35 쯤에 코드포스 라운드가 있길래 참여해보았다.

 

최근 코포에서 미끄러져서, 레이팅이 블루에서 다시 민트로 떨어져버렸는데

 

다시 블루로 돌아가겠다는 신념으로 참가한 라운드였다.

 

Div2는 총 7문제가 나왔고, 결과는 4솔

 

 

A번 문제 Changing Volume

https://codeforces.com/contest/1255/problem/A

 

Problem - A - Codeforces

 

codeforces.com

 

볼륨을 a에서 b로 변경해야 하는데, 볼륨은 음수가 불가능하고 버튼 한번을 눌러서 +- 1,2,5 중 하나의 액션을 할 수 있다.

 

총 6가지 액션이 가능한 셈이다.

 

버튼을 최소 횟수를 눌러서 볼륨을 a에서 b로 변경할 때, 최소 누르는 횟수를 알아내는 문제이다.

 

뭔가 bfs로 탐색을 해야 정확할 것 같은 느낌인데, 케이스를 나눠서 보면 그리디하게 풀린다.

 

 

만약 a와 b의 절댓값 차이가 5이하인 경우들을 체크해보자.

 

차이가 5인 경우 = +5를 한번 누름

차이가 4인 경우 = +2를 두번 누름 = 혹은 +5를 한번, -1를 한번 누름

차이가 3인 경우 = +2를 한번 +1를 한번 누름 = +5를 한번, -2를 한번 누름

차이가 2인 경우 = +2를 한번 누름 < +5를 한번, -2를 한번, -1를 한번 해서 총 3번 누름

차이가 1인 경우 = +1를 한번 누름 < +5를 한번 -2를 두번 해서 총 3번 누름

 

차이가 5초과인 경우는 5를 누르는 것이 최소로 줄이는 것이 자명한 상태이다.

 

차이가 5이하인 경우도, +5와 -를 조합하는 경우와 +만으로 누르는 경우를 비교했을 때, +만으로 조합하는 경우가 횟수가 작거나 같다.

 

따라서 차이값에 대하여 큰 단위부터 누르면 된다.

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
void solve() {
	int a, b;
	cin >> a >> b;
	int diff = abs(a - b);
	int ans = 0;
	ans += diff / 5;
	diff %= 5;
	ans += diff / 2;
	diff %= 2;
	ans += diff;
	cout << ans << endl;
}
int main() {
	int tc;
	cin >> tc;
	while (tc--) solve();
}

B번 문제 Fridge Locker

https://codeforces.com/contest/1255/problem/B

 

Problem - B - Codeforces

 

codeforces.com

논란이 많은 문제이다...

 

냉장고가 private 해 지기 위해서는, 서로다른 2개 이상의 냉장고와 연결된 체인이 있어야 한다.

이 조건은 모든 냉장고에 다 적용되어야 하므로, 최소 냉장고 개수와 동일한 수의 체인이 있어야 조건이 맞게 만들 수 있다. (체인 하나 당 2개의 냉장고를 연결하므로)

 

그리고 cost는 냉장고별 고유값을 더한 값이므로, 어차피 모든 냉장고는 고유값 * 2만큼은 코스트를 써야 한다는 점에

 

그냥 원형으로 체인을 덮은 뒤, 남는 체인들은 a값이 가장 낮은 냉장고에 걸어버리는 식으로 풀이를 했다.

 

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
typedef pair<int, int> pii;
pii a[2005];
void solve() {
	int n, m;
	cin >> n >> m;
	int total = 0;
	for (int i = 0; i < n; i++) {
		a[i].second = i + 1;
		cin >> a[i].first;
		total += a[i].first;
	}
	total *= 2;
	sort(a, a + n);
	if (m < n || n <= 2) {
		//impossible
		cout << -1 << endl;
		return;
	}
	int rest = m - n;
	total += rest * (a[0].first + a[1].first);
	cout << total << endl;
	for (int i = 1; i < n; i++) {
		cout << i << ' ' << i + 1 << endl;
	}
	cout << n << ' ' << 1 << endl;
	for (int i = 0; i < rest; i++) {
		cout << a[0].second << ' ' << a[1].second << endl;
	}
}
int main() {
	int tc;
	cin >> tc;
	while (tc--) {
		solve();
	}
}

 

라고 생각했지만, 엄청난 반례가 나타나게 되는데....

 

https://codeforces.com/blog/entry/71562

 

Tricky Case in 1255B (Fridge Lockers) if m>n - Codeforces

 

codeforces.com

위의 그림의 경우, 내가 만든 풀이법에 대한 반례가 된다. 남는 체인이 있는 경우 저런 경우가 생기는 것이다.

 

보통 이런 경우가 생기면 해당 코드포스 라운드는 Unrate가 되어 점수 반영이 되지 않게 되는데, 이번에는

 

해당 문제 오류때문에 피해를 본 사람만 따로 어필을 한 경우 그 해당 사람만 Unrate되도록 조치되었다.

 

어쨋든 문제를 푸는 도중에는 이러한 사실을 잘 몰랐다 ㅠ

어쩐지 공지가 계속 뜨더라...

C번 League of Leesins

https://codeforces.com/contest/1255/problem/C

 

Problem - C - Codeforces

 

codeforces.com

 

문제 본문은 좀 특이했던듯.

 

Permutation 원본에서, 원래 위치에서 3개씩 끊어서 자른 뒤 3개짜리 묶음의 순서와, 묶음 내 순서를 마구잡이로 섞은 것을 준다.

 

이를 통해 원본을 알아내야 하는데,

 

인접한 묶음들은 2개의 숫자가 동일하다는 것을 알 수 있다.

 

그리고 처음과 끝에 등장하는 숫자 하나는 단 한번만 등장한다는 특징이 있으므로, 이를 이용해서 하나하나 연결시켜서

 

찾아나가면 된다.

 

구현이 좀 귀찮았던 문제이다.

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
int arr[100005][4];
vector<int> pos[100005];
 
vector<int> tail, subtail;
int find_val(int a, int b, int no) {
	for (int i = 0; i < pos[a].size(); i++) {
		for (int j = 0; j < pos[b].size(); j++) {
			if (pos[a][i] == pos[b][j]) {
				int idx = pos[a][i];
				int candid = arr[idx][3] - a - b;
				if (candid != no) {
					return candid;
				}
			}
		}
	}
	assert(false);
	return -1;
}
int find_pos(int a, int b) {
	for (int i = 0; i < pos[a].size(); i++) {
		for (int j = 0; j < pos[b].size(); j++) {
			if (pos[a][i] == pos[b][j]) {
				return pos[a][i];
			}
		}
	}
	assert(false);
	return -1;
}
int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n - 2; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		arr[i][0] = a;
		arr[i][1] = b;
		arr[i][2] = c;
		arr[i][3] = a + b + c; //summation
		pos[a].push_back(i);
		pos[b].push_back(i);
		pos[c].push_back(i);
	}
	for (int i = 1; i <= n; i++) {
		if (pos[i].size() == 1) tail.push_back(i);
		else if (pos[i].size() == 2) subtail.push_back(i);
	}
 
	vector<int> ans;
	int no = tail[0];
	ans.push_back(no);
	vector<int> rest;
	for (int i = 0; i < 3; i++) {
		int val = arr[pos[no][0]][i];
		if (val == tail[0]) continue;
		rest.push_back(val);
	}
	int prev;
	if (pos[rest[0]].size() == 2) {
		ans.push_back(rest[0]);
		ans.push_back(rest[1]);
		prev = rest[0];
	}
	else {
		ans.push_back(rest[1]);
		ans.push_back(rest[0]);
		prev = rest[1];
	}
	while(ans.size() < n) {
		//int position = find_pos(prev, ans.back());
		//int next = arr[position][3] - prev - ans.back();
		int next = find_val(prev, ans.back(), no);
		no = prev;
		prev = ans.back();
		ans.push_back(next);
	}
	for (auto v : ans) {
		cout << v << ' ';
	}
	cout << endl;
	return 0;
}

 

D번 Feeding Chickens

https://codeforces.com/contest/1255/problem/D

 

Problem - D - Codeforces

 

codeforces.com

 

이것도 구현형 문제이다. 닭들이 Rice가 포함된 셀을 가장 비슷하게 가져가도록 만들면 된다.

 

전체 Rice 수를 R, 닭의 수를 K라고 할 때 //(floor(R/K)//)를 각자 주고, //(R - floor(R/K)*K//)만큼의 닭에게는 +1만큼 더 지급한다고 치면 된다.

 

그리고 땅 분할은 맨 위 row부터 지그재그로 가로로 훑고 내려오는 식으로 할당하면 된다.

 

첫줄을 왼쪽에서 오른쪽으로 훑고, 두번째 줄은 오른쪽에서 왼쪽으로 훑고, 그담에 다시 왼쪽에서 오른쪽

이런식으로 훑으면서 내려가면 인접한 땅을 할당하되 중복 할당 되지 않도록 할 수 있다.

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
vector<char> chick;
vector<int> needs;
string field[105];
int row_rices[105];
void solve() {
	int total_rices = 0;
	int r, c, k;
	cin >> r >> c >> k;
	memset(row_rices, 0, sizeof(row_rices));
	needs.clear();
 
	for (int i = 0; i < r; i++) {
		cin >> field[i];
		for (char c : field[i]) {
			if (c == 'R') row_rices[i]++;
		}
		total_rices += row_rices[i];
	}
 
	int each = total_rices / k;
	int rest = total_rices - each * k;
	for (int i = 0; i < rest; i++) {
		needs.push_back(each + 1);
	}
	for (int i = rest; i < k; i++) {
		needs.push_back(each);
	}
	int x, y, dir;
	x = y = 0;
	dir = +1;
	for (int i = 0; i < k; i++) {
		int need = needs[i];
		char ch = chick[i];
		int got = 0;
		while (got < need || i+1 == k) {
			if (y == -1) {
				dir = +1;
				y = 0;
				x++;
			}
			else if (y == c) {
				dir = -1;
				y = c - 1;
				x++;
			}
			if (x == r) break;
 
			if (field[x][y] == 'R') {
				got++;
			}
			field[x][y] = ch;
			y += dir;
		}
	}
	for (int i = 0; i < r; i++) {
		cout << field[i] << endl;
	}
	return;
}
int main() {
	for (char c = '0'; c <= '9'; c++) chick.push_back(c);
	for (char c = 'a'; c <= 'z'; c++) chick.push_back(c);
	for (char c = 'A'; c <= 'Z'; c++) chick.push_back(c);
	int tc;
	cin >> tc;
	while (tc--) solve();
 
	return 0;
}

 

E번부터는 풀지를 못했고 이해도 좀 덜 했는데, Editorial을 보면서 공부좀 해 보아야 겠다.

 

 

Editorial

https://codeforces.com/blog/entry/71594

 

Codeforces Round #601 Editorial - Codeforces

 

codeforces.com

 

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


백준 16563번 어려운 소인수 분해 문제이다.


2018년 서강대학교 교내 프로그래밍 경시대회 Master등급, 4학기 이하 학생만 참여하는 대회 문제라길래


한번 재미로 문제를 풀어봤다가 빤쓰런각 ㅠㅠ



문제를 보고, 에라토스테네스의 채를 이용해서 소수를 다 구한 뒤, 소수들 리스트로 소인수 분해를 하면 되겠다 생각했었다.



근데 이 문제에서 소인수 분해하는 수의 개수가 꽤 많다는 점이, 내가 에라토스테네스의 채를 제대로 이해하지 못하고 있다는 것을 알게 해 주었다 ㅠ


문제 분석


말 그대로 소인수 분해를 하면 된다. 다만 소인수 분해를 하는 횟수와, 수의 크기가 꽤 되기 때문에, 소수들을 일일이 하나씩 구하다가는 TLE가 나기 십상이다.


500만이라는 수 보다 작은 소수들을 빠르게 한번 다 구해버린 뒤 (전처리 과정), 소인수 분해를 차차착 해주면 되는데


이정도 범위는 배열로 메모리를 잡을 수 있으므로, 에라토스테네스의 채를 이용하면 좋다.


https://ko.wikipedia.org/wiki/%EC%97%90%EB%9D%BC%ED%86%A0%EC%8A%A4%ED%85%8C%EB%84%A4%EC%8A%A4%EC%9D%98_%EC%B2%B4



내가 조금 잘못 알고 있던 개념

사실 완전히 잘못 알고 있던 것은 아니고, 에라토스테네스의 채에서 어느정도 스킵하고 지나가야 하는 부분도, 잘 못 믿고 더블체크를 하는 과정이 항상 있었다.

이 과정에서 조금 비효율적이었지만, 크게 상관은 없는 그런.. 상황이었다.

에라토스테네스의 채는 알려진 소수를 하나 알게 되면, 그 소수의 배수들을 모조리 없애버리는 소거식으로 한 뒤, 남은 수들은 소수라고 보는 방식이다.

N=500만, 인 상황에서 N보다 작은 모든 소수를 구해야 하는 상황이다.

기존에 내가 잘못 알고 있던 부분은 2부터 시작해서 채에 걸러지지 않은 녀석들이 소수가 맞는지 2부터 sqrt(n)까지의 수로 다시 나누어서 확인하고 있었는데, 이 부분 때문에 TLE가 많이 났다.

2부터 이미 채에 걸러지지 않았다면, 그냥 소수가 맞다고 보면 된다.


왜 그런가 하면..

//(N//)이하의 소수를 다 구한다고 하면, //(\sqrt{N}//)이하의 소수의 배수들을 다 소거했을 때, //([\sqrt{N}, N]//) 범위에 있는 걸러지지 않은 모든 수는 소수가 맞다.

왜 그런가 하면 귀류법으로 증명이 가능하다.

만약 //([\sqrt{N}, N]//)사이에 걸러지지 않았지만, 소수가 아닌 합성수가 존재한다고 가정해 보자.

이 수를 K라고 하겠다.

이 수는 합성수이므로 //(K=p*q//)로 인수분해가 가능할 것이다. //(p, q//)는 소수 일 수도 있고 아닐 수도 있다.

어쨋든 이 사실로 인하여, //(min(p, q) <= \sqrt{N}//)라는 사실이 성립한다. 그렇다는 것은 //(p//)나 //(q//)중 하나의 수는

소수의 배수들을 소거할 때 무조건 한번은 출연을 했다는 것이다. 

//(p//)나 //(q//)중 작은 수가 합성수라면, 이미 그 보다 작은 소수의 배수로서 등장을 했을 것이고, 소수라고 하더라도

모든 소수가 다 걸러졌기 때문에 걸려졌을 것이다.

그러므로, 해당 범위 //([\sqrt{N}, N]//)에는 합성수가 남아있을 수 없다.

뭐 이런 결론이 된다.

" //(\sqrt{N}//)이하의 소수의 배수들을 다 소거했을 때," 이 사실이 참이라면 성립하게 된다.

이런식으로 Inductive step이 성립하게 되었으니, 수학적 귀납법으로도 확장이 가능할 것으로 보인다.

뭐 그래서 맞을 것이다. 

맞겟지 아마 증명이?

틀린 부분이 있다면 얼마든지 지적, 조언 등 환영합니다.

그래서 제곱근까지의 소수로만 확장시켜도 그 위도 다 소수가 남아있는게 맞다고 합니다.




그리고 소수를 찾아서 그 배수들을 소거할 때, 만약 소수 //(p//)를 찾았다면 다음 첫 소거 아이템은 //(p^2//)부터 시작해도 된다고 한다.

이미 그 이전 것들은 즉, //(p, p*2, p*3, ... , p*(p-1)//)는 다 소거가 되었다고 볼 수 있다. 오른쪽에 붙는 //([1, p-1]//)에 해당하는 수 들은

소수이면 이미 다 체크되었고, 합성수라면 또 어느 소수의 배수로 다 체크되었기 때문에 다 걸려있다 이말입니다. ㅠ

하 이 부분을 제대로 짚고 공부하지 않아서 이렇게 빵꾸가 나버린 논리 ㅠ



고로 구현한 코드는..


그런점을 이용하면 소수를 쉽게 구할 수 있다.

그리고 채를 bool 배열로만 잡았었는데, int로 잡아서 다음과 같게도 구현이 가능하더라..

int배열에 들어가는 값은, int arr[i]라 하면 i가 갖는 가장 작은 소인수 값이 해당 배열에 들어가게 하면 쉽~게 소인수 분해를 하도록 만들 수 있다더라 ㅠ


#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;

int min_factor[5000005];
void solve(int n) {
	while (n > 1) {
		printf("%d ", min_factor[n]);
		n /= min_factor[n];
	}
	putchar('\n');
}
int main() {
	min_factor[0] = min_factor[1] = -1;
	for (int i = 2; i <= 5000000; i++) {
		min_factor[i] = i;
	}

	for (int v = 2; v * v <= 5000000; v++) {
		if (min_factor[v] == v) { //v가 소수라면
			for (int k = v * v; k <= 5000000; k += v) {
				if (min_factor[k] == k)
					min_factor[k] = v;
			}
		}
	}
	
	int n;
	scanf("%d", &n);
	while (n--) {
		int v;
		scanf("%d", &v);
		solve(v);
	}
	return 0;
}

하하 반성합시다~

하나라도 제대로 이해하면서 공부해야 합니다~ ㅠㅠ


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


백준 저지 2529번 문제, 부등호 문제이다.


문제 접근

부등호에 맞는 숫자들 배열들 중 가장 큰 값과 작은 값을 구하면 된다.

0으로 시작해도 친다고 하므로, 예외처리를 덜 귀찮게 해주는 그런 옵션이라고 보면 된다.

k가 9로 값이 작아서 10의자리 수가 나올 것이므로 완전탐색 같은걸로 풀릴 것 같은 냄새가 난다.


뭔가 이런 문제는 최대한 쉽게 구현해서 풀어버리고 싶었다. (시간복잡도 최적화따위는 통과만 할 정도면 충분하다!!) 란 마인드?!



시간복잡도와 알고리즘 계획


일단 문제를 대충읽었을때는 10자리수 0000000000 ~ 9999999999 까지 생각을 했었다.

물론 이 수를 다 체크하면 TLE도 나며, 

9,999,999,999라는 수는 일단  100억 정도 되는 수 이다.


1초에 1억번 연산을한다고 보면 100초이니까 절때 안되는 계산량이다.



근데 문제를 다시 읽어보니 0부터 9까지 수가 최대 1번씩만 나온다고 한다.


그러면 최악의 경우 10!라고 볼 수 있고 이 수는, 3,628,800이다. 360만 정도인데


각각의 수를 붙여서 10자리수 스트링을 만들고, 정수를 만들어서 체크를 하려면 체크할때마다 10의 연산량, 대충 for-loop 10번을 도니까


360만에 x 10해서 3600만 정도 된다.


약간 간당간당해진다.


(실제로 요런식으로 구현하니까 생각보다 꽤 느리더라.)


그래서 여기서 더 경우의 수를 줄이도록 프루닝(Pruning : 가지치기)를 해 보면,


숫자를 만들다가 부등호 방향에 부합하지 않으면 그 쪽은 더 이상 해 볼 가치가 없으므로 그쪽은 탐색하지 않으면 된다.


부등호 기댓값으로 대충 절반정도 컷팅이 될 것 같다.


시간복잡도는 //(O(n!)//)이 그대로 나오긴 할 것 같다.




구현 코드

구현은 일반적은 dfs를 돌리면 된다.


재귀함수를 이용해서 간편하게 작성해도 되고, 중간에 프루닝 하는 부분들이 들어가야 한다.


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
vector<string> val;
string largest, smallest;
ll large, small;
bool used[11];
int value[11];
int n;
void dfs(int pos) {
	if (pos == n + 1) {
		string tmp = "";
		ll cur_val = 0;

		for (int i = 0; i <= n; i++) {
			cur_val *= 10;
			tmp += (char)value[i] + '0';
			cur_val += value[i];
		}
		if (cur_val < small) {
			small = cur_val;
			smallest = tmp;
		}

		if (cur_val > large) {
			large = cur_val;
			largest = tmp;
		}

		return;
	}

	for (int i = 0; i < 10; i++) {
		if (used[i] == false) {
			if (pos > 0) {
				if (val[pos - 1] == "<" && value[pos - 1] > i) continue;
				if (val[pos - 1] == ">" && value[pos - 1] < i) continue;
			}
			used[i] = true;
			value[pos] = i;
			dfs(pos + 1);
			
			used[i] = false;
		}
	}
}
int main() {
	small = 9876543210;
	cin >> n;
	val.resize(n);
	for (int i = 0; i < n; i++) {
		cin >> val[i];
	}
	dfs(0);
	cout << largest << endl << smallest << endl;

	return 0;
}




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

 

9663번: N-Queen

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다. N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.

www.acmicpc.net

 

문제분석

백준 9663번 N-Queen 문제입니다. 굉장히 유명한 문제 중 하나입니다.

 

완전탐색으로 풀 수 있는것으로 알려져있습니다.

 

N이 15인데, 그러면 체스 말 판이 15x15=225칸이 있고, 이 중 15칸을 고른다고 하면

 

경우의 수가 //(\scriptstyle 225\textstyle C\scriptstyle 15//) 가 되는데, 뭐, 대충 계산해봐도

 

1억이라는 수를 훌쩍 넘는 것을 알 수 있습니다. //(100^{10}//)라고만 봐도 //(10^{20}//)이 되니깐, 1억이 //(10^8//)인 것을 감안하면 안되는 경우의 수가 되지요.

 

여기서 무조건 아닌 수를 조금 줄여보도록 하면,

 

퀸이 같은 행이나 열에 있으면 공격할 수 있으므로 각자 다른 열이나 행에 두어야 합니다.

 

그러면 NxN의 체스판에 퀸을 N개를 두려면 1행에 있는 퀸, 2행에 있는 퀸... N행에 있는 퀸이 최대 1개씩 밖에 있을 수 없습니다.

 

그리고 열에도 퀸은 최대 1마리만 둘 수 있기 때문에 이 조건을 맞추어 퀸을 배열하는 경우의 수는

 

1부터 N까지의 수를 한 줄로 나열하는 경우의 수와 같다고 볼 수 있습니다.

 

즉 //(N!//)이 되지요.

 

예를들어 //(N=4//)이고 //(1,3,2,4//)와 같이 배열한다는 것은 퀸을 (1,1), (2,3), (3,2), (4,4)에 배열한다는 뜻으로 볼 수 있습니다.

 

 

 

여기서 //(N=15//)일 때 //(15!=1307674368000//)인데 이 수도 여전히 큽니다.

 

하지만 퀸을 배치하다가 대각선으로 서로 공격할 수 있는 경우, 더이상 탐색하지 않는 가지치기(pruning)을 적용을 하면 저 수 보다는 적은 수의 탐색으로 경우의 수를 모두 찾아 볼 수가 있습니다.

 

구현전략

일단 완전탐색 문제이므로 제귀를 활용한 dfs로 탐색을 하면 쉽게 구현을 할 수 있습니다.

 

N개의 수를 나열하는 문제에 + 퀸이 대각선으로 공격할 수 있다는 점을 체크를 해야 합니다.

 

같은 대각선에 있는 좌표는 x좌표+y좌표의 값이 같거나, x좌표-y좌표의 값이 같습니다. 

 

이게 왜 그런가 하는지는 엑셀 등에 직접 좌표마다 값을 구해서 눈으로 보면 간단하며,

 

1차함수의 그래프를 그려봐도 마찬가지로 쉽게 알 수 있습니다.

 

x+y=k의 그래프를 그리면 2차원 좌표계에서 좌측상단에서 우측하단으로 뻗는 직선의 그래프를 확인할 수 있습니다.

x-y=k의 그래프 역시 2차원 좌표계에서 우측상단에서 좌측하단으로 뻗는 직선의 그래프를 확인할 수 있습니다.

 

이러한 점을 고려해서 제귀로 구현하면 생각보다 간단하게 구현할 수 있습니다.

 

코드

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
/*
N=15일때
좌표계가 (0,0) ~ (14,14)라고 볼때,
*/
int pl[35]; // 0 ~ 28
int mi[35]; // 14 ~ -14
bool num[15];
int val[15];
int ans;
int n;
void dfs(int x) {
	if (x == n) {
		ans++;
		return;
	}
	for (int y = 0; y < n; y++) {
		if (num[y] == false && pl[x+y] == false && mi[x-y+14] == false) {
			val[x] = y;
			num[y] = pl[x + y] = mi[x - y + 14] = true;
			dfs(x + 1);
			num[y] = pl[x + y] = mi[x - y + 14] = false;
		}
	}
}
int main() {
	cin >> n;
	dfs(0);
	cout << ans << endl;
}

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

 

2751번: 수 정렬하기 2

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 절댓값이 1,000,000보다 작거나 같은 정수이다. 수는 중복되지 않는다.

www.acmicpc.net

위 문제의 정답코드이기도 합니다.

 

코드 복사를 하려면 gist의 우측 하단의 view-raw를 누른 뒤 복사하시면 됩니다.

+ Recent posts