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

 

17406번: 배열 돌리기 4

크기가 N×M 크기인 배열 A가 있을때, 배열 A의 값은 각 행에 있는 모든 수의 합 중 최솟값을 의미한다. 배열 A가 아래와 같은 경우 1행의 합은 6, 2행의 합은 4, 3행의 합은 15이다. 따라서, 배열 A의 값은 4이다. 1 2 3 2 1 1 4 5 6 배열은 회전 연산을 수행할 수 있다. 회전 연산은 세 정수 (r, c, s)로 이루어져 있고, 가장 왼쪽 윗 칸이 (r-s, c-s), 가장 오른쪽 아랫 칸이 (r+s, c+s)인 정사각형을 시계

www.acmicpc.net

백준저지 17406번 문제 배열 돌리기4 문제 풀이입니다.

 

K가 6밖에 안되므로, 6개를 줄세우는 가짓수는 6!=720개 뿐입니다.

 

따라서 완전탐색을 하면 되는 구현위주의 문제입니다.

 

Rotation시키는 것은, 돌아가는 원판에 있는 숫자들을 다 저장해놓고, 한칸씩 더 이동시켜서 다시 넣는 방식으로 했으며

 

왼쪽위 오른쪽위, 오른쪽아래, 왼쪽아래 꼭지점을 기준으로

 

해당 꼭지점에 다다를때 이동방향을 바꾸는 식으로 구현해보았습니다.

 

 

#include <bits/stdc++.h>
using namespace std;

typedef pair<int, int> pii;
int A[55][55];
typedef struct _Rot {
	int r, c, s;
} Rot;
Rot rot[10];
int N, M, K;
int ans;
void get_input();
bool used[10];
int order[10];
void copy(int A[][55], int B[][55]);
const int dx[] = { +0, +1, +0, -1 };
const int dy[] = { +1, +0, -1, +0 };

void _rotate(int B[][55], int row, int col, int rad) {
	vector<int> val;
	int x = row - rad;
	int y = col - rad;
	int cnt = rad * 8;
	set<pii> check;
	check.insert(make_pair(row + rad, col + rad));
	check.insert(make_pair(row - rad, col + rad));
	check.insert(make_pair(row + rad, col - rad));
	int k = 0;
	for (int i = 0; i < cnt; i++) {
		val.push_back(B[x][y]);
		x += dx[k];
		y += dy[k];
		if (check.find(make_pair(x, y)) != check.end()) {
			k++;
		}
	}
	k = 0;
	x = row - rad;
	y = col - rad;
	x += dx[0];
	y += dy[0];
	for (int i = 0; i < cnt; i++) {
		B[x][y] = val[i];
		x += dx[k];
		y += dy[k];
		if (check.find(make_pair(x, y)) != check.end()) {
			k++;
		}
	}
}
void rotate(int B[][55], int row, int col, int rad) {
	for (int i = 1; i <= rad; i++) {
		_rotate(B, row - 1, col - 1, i);
	}
}
void dfs(int p) {
	if (p < K) {
		for (int i = 0; i < K; i++) {
			if (used[i] == false) {
				used[i] = true;
				order[p] = i;
				dfs(p + 1);
				used[i] = false;
			}
		}
	}
	else {
		//do simulation
		int B[55][55];
		copy(B, A);
		for (int i = 0; i < K; i++) {
			auto& o = rot[order[i]];
			rotate(B, o.r, o.c, o.s);
		}
		
		for (int i = 0; i < N; i++) {
			int sum = 0;
			for (int j = 0; j < M; j++) {
				sum += B[i][j];
			}
			ans = min(ans, sum);
		}

		
	}
}
int main() {
	ans = 987654321;
	get_input();
	dfs(0);
	cout << ans << endl;
}

void get_input() {
	cin >> N >> M >> K;
	for (int i = 0; i < N; i++) 
		for (int j = 0; j < M; j++) 
			cin >> A[i][j];
		
	for (int i = 0; i < K; i++) {
		auto& R = rot[i];
		cin >> R.r >> R.c >> R.s;
	}
}
void copy(int A[][55], int B[][55]) {
	for (int i = 0; i < N; i++)
		for (int j = 0; j < M; j++) {
			A[i][j] = B[i][j];
		}
}

https://atcoder.jp/contests/abc131

https://img.atcoder.jp/abc131/editorial.pdf

 

 

앳코더 비기너 컨테스트 131 문제들 풀이입니다.

 

오랜만에 PS 문제좀 풀어볼려고 앳코더에서 비교적 난이도가 쉬운 beginner contest 를 하나 골라서 업솔빙 해보았습니다.

 

원래 컨테스트일때는 참여 자체를 안하고, 남는 시간에 하나하나 풀어보았는데, D번까지는 쉽게 풀었고, E번부터는 힌트가 꽤 필요했습니다.

 

Editorial을 찾아보았는데, 일본어 버전만 제공되는 것 같아서 한국어로 풀이를 작성해보도록 하겠습니다.

 

https://atcoder.jp/contests/abc131/tasks/abc131_a

 

A - Security

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

A번 문제입니다. 인접한 녀석이 같은 값이 있는지 아닌지만 확인하면 되므로, 매우 간단한 문제입니다. 그냥 그대로 구현하면 됩니다.

 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
int main() {
	string s;
	cin >> s;
	char p = s[0];
	for (size_t i = 1; i < s.length(); i++) {
		if (s[i] == p) {
			cout << "Bad";
			return 0;
		}
		p = s[i];
	}
	cout << "Good";
}

 

https://atcoder.jp/contests/abc131/tasks/abc131_b

 

B - Bite Eating

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

B번 문제입니다. 사과의 풍미(flavor) 값은 연속된 수열 값으로 나타납니다. 전체 값 중에서 사과 하나를 뺏을 때 차이가 가장 적을려면, 전체 값 중에 절대값이 가장 작은 것을 하나 빼버리면 됩니다.

O(N)으로 해결할 수 있습니다.

 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
int main() {
	int N, L;
	cin >> N >> L;
	int S = (N - 1) * N / 2 + N * L;
	int dect = 987654321;
	for (int i = 0; i < N; i++) {
		int cand = i + L;
		if (abs(cand) < abs(dect)) {
			dect = cand;
		}
	}
	cout << (S - dect) << endl;
}

https://atcoder.jp/contests/abc131/tasks/abc131_c

 

C - Anti-Division

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

C번 문제입니다. 포함배제 Counting 문제입니다.

 

값 C로도 나누어 떨어지지 않고 D로도 나누어 떨어지지 않는 수의 개수는

(전체) - (C로 나누어 떨어지는 수) - (D로 나누어 떨어지는 수) + (C와 D 둘다 나누어 떨어지는 수)

가 됩니다.

 

C와 D 둘다 나누어 떨어지는 수는 C와 D의 LCM(최소공배수)으로 나누어 떨어지는 수와 같습니다.

C와 D의 최소 공배수는 C*D / gcd(C, D)와 같습니다.

gcd(C, D)는 C와 D의 최대공약수를 뜻합니다.

#include <bits/stdc++.h>
#include <algorithm>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
template<class T>
T gcd(T a, T b)
{
	if (b == 0) return a;
	a %= b;
	return gcd(b, a);
}
int main() {
	ll A, B, C, D;
	cin >> A >> B >> C >> D;
	ll total = B - A + 1;
	ll Cdiv = B / C - ((A - 1) / C);
	ll Ddiv = B / D - ((A - 1) / D);
	ll LCM = C * D / gcd(C, D);
	ll CDdiv = B / LCM - ((A - 1) / LCM);
	ll ans = total - Cdiv - Ddiv + CDdiv;
	cout << ans << endl;
}

https://atcoder.jp/contests/abc131/tasks/abc131_d

 

D - Megalomania

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

D번 문제입니다. 모든 일을 다 끝낼 수 있는지 아닌지만 알아내면 됩니다.

단순한 그리디 문제입니다. 데드라인이 앞에 있는, 녀석들을 먼저 끝내는 식으로 진행을 하고, 그 중간에 데드라인을 넘어가는 녀석이 생기면 실패, 아니면 성공입니다.

 

이를 어떻게 알 수 잇냐면, 데드라인이 빠른녀석을 먼저 처리하지 않는 경우를 고려했을때,

데드라인이 빠른 녀석을 먼저 처리하는 경우로 변형이 가능하고, 변형을 했을 때 변형하기 전 보다 무조건 상태가 비슷하거나 나아진다는 것을 증명하면 됩니다.

 

데드라인이 빠른 순서대로 sorting이 되어 있는 상태라고 가정해 봅시다.

뭐 다음과 같이 있겠죠

 

$$b_{1},\space b_{2},\space b_{3},\space b_{4},\space b_{5},\space b_{6}\space ... \space b_{n-1},\space b_{n}$$

데드라인이 더 느린 녀석을 먼저 하나 처리를 했는데도 모두 처리를 가능한 상황이라고 가정해 보겠습니다.

$$b_{1},\space b_{2},\space b_{6},\space b_{4},\space b_{5},\space b_{3}\space ... \space b_{n-1},\space b_{n}$$

6번이랑 3번이 순서가 바뀌어서 처리 되어도 모두 처리가능한 상황이라는 것이죠.

 

여기서 다시 원래 순서, 1,2,3,... 순서로 바꾼다고 가정해봅시다. 3과 6번을 다시 순서를 바꾸는거죠

 

일단 바뀌는 두 당사자 뒤에 처리되는 녀석들은, 즉 6번 이후 녀석들은 처리되는 시기가 변화가 없으므로 상관이 없습니다. 처리되는 시기는 1번 부터 6번의 처리시간을 다 합친 이후에 처리가 될 것이므로 변화가 없습니다.

 

데드라인이 빠른 녀석(3)은 늦게 처리되어도 되던 상황에서 더 빨리 처리가 되었고,

그래서 그 녀석 때문에 문제가 될 일은 없습니다.

 

데드라인이 느린 녀석(6)은 더 뒤에 처리되게 되었는데, 자신보다 데드라인이 빠른 녀석도 처리가 되었던 위치이므로, 문제없이 처리가 되게 됩니다.

 

따라서 둘의 위치를 바꾸더라도 문제없이 처리가 가능합니다.

 

두번째 경우는 아래 순서로 처리할 경우 실패하는 경우입니다.

$$b_{1},\space b_{2},\space b_{6},\space b_{4},\space b_{5},\space b_{3}\space ... \space b_{n-1},\space b_{n}$$

위 순서대로 처리를 하면 실패를 합니다.

다시 그래서 3번과 6번을 바꾸어서 처리한다고 봅시다.

마찬가지로 6번 이후 녀석들은 처리 시기가 바뀌지 않아서 신경 쓸 필요가 없습니다. 3번 이전 녀석도 마찬가지죠.

3번이 앞으로 오게 되는데, 3번이 앞으로 오면서 여기서 만약 데드라인이 걸린다고 칩시다.

그런데 기존에는 6번이 3번째 자리에 있었는데, 6번의 데드라인이 3번 데드라인보다 길기 때문에 3번이 앞으로 와서 데드라인이 걸렸다면, 6번 자리에 있었을 경우에도 무조건 데드라인이 걸렸을 것입니다.

따라서 3번이 바뀐 자리때문에 걸렸다면 어차피 걸릴 상황이란거죠. 따라서 밑져야 본전인 상황입니다.

 

6번이 뒤로 가면서 걸렸다면 3번이 뒤에 있었을 때도 걸렸을 것입니다. 3번의 데드라인이 더 짧거든요. 이 경우에도 밑져야 본전입니다.

 

그리고 바꾸기 전에는 3번이 6번자리에서 데드라인에 걸리지만, 바꾸고 나서는 안걸리는 케이스가 있을 수 있습니다. 따라서 이 경우에는 기존에는 실패지만, 바꾸고 나서 성공이 되므로 좀 더 나은 경우이죠.

 

약간 증명이 중구난방이긴한데 어쨋든, 이런식으로 증명이 가능합니다.

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
 
int main() {
	int n;
	cin >> n;
	vector<pii> a(n); // deadline, duration
	for (int i = 0; i < n; i++) {
		cin >> a[i].second >> a[i].first;
	}
	sort(a.begin(), a.end());
	int elap = 0;
	for (int i = 0; i < n; i++) {
		elap += a[i].second;
		if (elap > a[i].first) {
			cout << "No";
			return 0;
		}
	}
	cout << "Yes";
	return 0;
}

 

 

 

https://atcoder.jp/contests/abc131/tasks/abc131_e

 

E - Friendships

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

E번 문제입니다. 문제 내용을 잘못 이해해서 좀 해맸었는데, 일단 N개의 vertex로 shortest path = 2인 pair를 최대한 많이 만드는 구성을 한번 생각해봤습니다.

 

N=7이라 한다면, 대충 위와 같이 생겼을 때 가장 많은 shortest path = 2인 쌍이 생깁니다. 1을 제외한 모든 조합들이 가능한거죠.

즉 //(\dbinom{6}{2}//)개 만큼 생깁니다. 

 

N에 대하여 일반화를 하면 //(\dbinom{N-1}{2}//)가지가 나오는 것이죠. 요렇게 나오는 경우가 최대 경우입니다.

 

따라서 //(K > \dbinom{N-1}{2} //)이면 불가능한 경우입니다.

 

그렇다면 저 최대값 이하의 값은 다 만들수가 있느냐? 

 

이에 대한 답은 Yes입니다. 위 그림에서 예를 들어 4와 5를 연결해버리면, 4에서 5로 가는 최단거리가 1이 되어서, 만족하는 쌍의 수가 하나 줄게 됩니다. 그리고 그 추가된 Edge는 다른 pair들에는 전혀 영향을 끼치지 않죠. 이런식으로 하나하나씩 줄여서 K값을 0으로 만들수도 있습니다.

 

따라서 N값에 따라서 0~최대값까지 죄다 만들 수 있는 것이죠. 이 범위 안에 들어있다면 저런 형태의 graph를 리턴하고, 그렇지 않다면 불가능하다라고 출력하면 됩니다.

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
int main() {
	int N, K;
	cin >> N >> K;
	int maxi = (N - 1)*(N - 2) / 2;
	if (K > maxi) {
		cout << -1;
		return 0;
	}
	int M = N - 1;
	int diff = maxi - K;
	cout << (M + diff) << endl;
	for (int i = 0; i < M; i++) {
		cout << 1 << ' ' << i + 2 << endl;
	}
	int src = 2;
	int dst = 3;
	for (int i = 0; i < diff; i++) {
		cout << src << ' ' << dst << endl;
		dst++;
		if (dst == N + 1) {
			src++;
			dst = src + 1;
		}
	}
}

 

https://atcoder.jp/contests/abc131/tasks/abc131_f

 

F - Must Be Rectangular!

AtCoder is a programming contest site for anyone from beginners to experts. We hold weekly programming contests online.

atcoder.jp

F번입니다. 꽤나 어려웠는데요. 규칙을 찾아내면 생각보다는 간단합니다.

 

코드포스에서 풀이 올리신분들의 이야기를 보면 대충 다음과 같은 식으로 풀이합니다.

 

1. 점들을 graph로 모델링한다

2. 각각의 점은 vertex

3. 각각의 점과 같은 x좌표 혹은 y좌표에 있는 점 사이에 edge가 있다고 친다.

4. component들을 dfs혹은 bfs로 찾아낸다.

5. 각각 component에서 서로 다른 x좌표 개수와 y좌표 개수를 샌 뒤 그 둘을 곱한 개수를 모두 더한다.

6. 모두 더한 그 수가, 해당 operation을 최대한 사용해서 만들 수 있는 점들의 개수이다.

7. 여기서 원래 있던 점 수를 빼면 정답

 

요녀석이 왜 되는지 한번 생각을 해 보도록 합시다. 직접 종이에 그려보다 보면 대충 규칙성이 보입니다.

 

같은 x좌표, y좌표에 2개 이상의 점이 있으면, operation으로 한 점을 더 만들고, 그녀석으로 또 만들고 이러한 반복 작업을 통해서 바둑판식으로 점들을 모두 매워버릴 수 있습니다.

 

이런 성질을 관찰함으로써 위와 같은 풀이가 나오게 됩니다.

 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
#define MAXN 100005
bool chkX[MAXN], chkY[MAXN], visit[MAXN];
vector<int> xline[MAXN], yline[MAXN];
int X[MAXN], Y[MAXN];
int main() {
	int N;
	cin >> N;
	ll ans = -N;
 
	for (int i = 0; i < N; i++) {
		int x, y;
		cin >> x >> y;
		X[i] = x;
		Y[i] = y;
		xline[x].push_back(i);
		yline[y].push_back(i);
	}
 
	for (int i = 0; i < N; i++) {
		if (visit[i]) continue;
		visit[i] = true;
		set<int> xdist, ydist;
		xdist.clear();
		ydist.clear();
 
		queue<int> q;
		q.push(i);
		while (q.size()) {
			int c = q.front(); q.pop();
			int x = X[c];
			int y = Y[c];
			xdist.insert(x);
			ydist.insert(y);
 
			if (chkX[x] == false) {
				chkX[x] = true;
				for (auto nx : xline[x]) {
					if (visit[nx] == false) {
						visit[nx] = true;
						q.push(nx);
					}
				}
			}
 
			if (chkY[y] == false) {
				chkY[y] = true;
				for (auto ny : yline[y]) {
					if (visit[ny] == false) {
						visit[ny] = true;
						q.push(ny);
					}
				}
			}
		}
 
		ans += (ll)xdist.size() * (ll)ydist.size();
	}
 
	cout << ans << endl;
	return 0;
 
}

결론만 말하자면, 820등으로 Round 2로 진출

 

Round 1에서 1 Solved로 떨어진 작년과 비교하면 발전이 있기는 했었다.

 

이번 라운드는 오후 6시부터 8시 반까지 진행되었는데, 8시 30분부터 불꽃 축재를 보러 가야하기 때문에 사실상 8시까지만 문제를 풀 수 있었다.

 

최대한 집중해서 문제를 풀었는데, 배점을 확인해서 난이도가 1 < 2 < 3 인것을 알고 1번부터 풀이를 시작했다.

 

- Robot Programming Strategy

문제를 읽어보고 토너먼트 방식이라는 점에 꽤나 복잡한 문제라고 생각해보았는데, 사실 모든 적들을 다 이길 수 있는 조합을 만들어 내면 된다.

 

Strategy가 끝에 다달으면 다시 Cyclic하게 순환된다는 점에 유의해야 한다. Brute-Force로 Visible만 풀려고 했는데, 생각보다 N값이 작아서 Hidden 도 풀렸다.

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

set<string> enemy;
int main() {
	int tc; cin >> tc;
	for (int t = 1; t <= tc; t++) {
		int A;
		cin >> A;
		enemy.clear();
		string ans = "";
		bool ok = true;
		for (int i = 0; i < A; i++) {
			string s; cin >> s;
			enemy.insert(s);
		}
		for (int i = 0; i < 500; i++) {
			int R, S, P;
			R = S = P = 0;
			for (auto s : enemy) {
				char c = s[i % (int)s.length()];
				if (c == 'R') R++;
				else if (c == 'S') S++;
				else if (c == 'P') P++;
			}
			int C = 0;
			C += R > 0 ? 1 : 0;
			C += S > 0 ? 1 : 0;
			C += P > 0 ? 1 : 0;
			if (C == 3) {
				ok = false;
				break;
			}
			else if (C == 1) {
				if (R) ans += "P"; //바위에는 보
				else if (S) ans += "R"; //가위에는 바위
				else ans += "S"; //보에는 가위
				break;
			}
			else {
				//vector<string> victim;
				if (R == 0) { //보와 가위
					ans += "S";
					//i번째에 P인놈들 삭제
					for (auto s : enemy) {
						char c = s[i % (int)s.length()];
						if (c == 'P') {
						    enemy.erase(s);
							//victim.push_back(s);
						}
					}
				}
				else if (S == 0) {
					ans += "P"; //바위와 보
					//i번째에 R인놈들 삭제
					for (auto s : enemy) {
						char c = s[i % (int)s.length()];
						if (c == 'R') {
						    enemy.erase(s);
							//victim.push_back(s);
						}
					}
				}
				else if (P == 0) {
					ans += "R"; //가위와 바위
					//i번째에 S인놈들 삭제
					for (auto s : enemy) {
						char c = s[i % (int)s.length()];
						if (c == 'S') {
						    enemy.erase(s);
							//victim.push_back(s);
						}
					}
				}
				//for (auto s : victim) {
				//	enemy.erase(s);
				//}
				//victim.clear();
			}
		}

		printf("Case #%d: %s\n", t, ok == false ? "IMPOSSIBLE" : ans.c_str());
	}
}

 

- Power Arrangers

Permutation 순열 관련된 문제였다. 백준에서 K-번째 시리즈를 풀이하면서 대충 풀이가 영감이 왔는데 풀이는 다음과 같다.

나타난 순열들의 첫번째 글자들이 나타난 횟수를 모두 센다 -> 다 4!번(24) 나타나고 하나만 23번 나타난다. 그러면 23번 나타난 녀석이 첫번째 글자다.

 

23번 나타난 녀석들을 다시 확인해보면 나머지는 다 3!번(6) 나타나고 하나는 5번 나타난다. 그러면 두번째 글자를 알아낼 수 있다.

 

6번 나타난 녀석은 나머지는 2!번(2) 나타나고, 하나는 1번 나타난다. 세번째 글자는 1번 나타난 녀석이다.

 

마지막에는 1번 나타나게 되는데, 0번 나타난 녀석이 4번째 글자고 남는녀석이 5번째 글자가 된다.

 

Python으로 작성해보려다가 답답해서 다시 C++로 작성했다. Visible만 맞추고 갈려고 했는데 Hidden 도 맞는 솔루션이었다.

 

디버깅이 까다로웟는데, 파일 입출력으로 로그를 찍어서 확인하면서 디버깅했다.

 

#include <bits/stdc++.h>
using namespace std;
int value[] = {23, 5, 1, 0};
#define endl '\n'
int main() {
	// ofstream fout("output.txt");
	int T, F;
	setbuf(stdout, NULL);
	setbuf(stdin, NULL);
	cin >> T >> F;
	
	for (int t=1; t<=T; t++) {
		set<int> left;
		left.clear();
		left.insert(0);
		left.insert(1);
		left.insert(2);
		left.insert(3);
		left.insert(4);
		vector<int> index[5];
		vector<int> ans;
		int cnt[5];
		ans.clear();
		memset(cnt, 0, sizeof(cnt));
		for (int i=0; i < 5; i++) index[i].clear();
		
		for (int i=1; i <=595; i+=5) {
			cout << i << endl;
			string s;
			cin >> s;
			char c = s[0];
			cnt[c-'A']++;
			index[c-'A'].push_back(i);
		}
		int idx = 0;
		for (int i=0; i < 5; i++) {
			if (cnt[i] == value[0]) {
				idx = i;
				break;
			}
		}
		// fout << "CNT = {" << cnt[0] << "," << cnt[1] << "," << cnt[2] << "," <<cnt[3] << "," <<cnt[4] << "}" << endl;
		ans.push_back(idx);
		left.erase(idx);
		// fout << idx << endl;
		vector<int> next;
	
		for (int kk = 1; kk <4; kk++) {
			next.clear();
			for (int q : index[idx]) {
				next.push_back(q+1);
			}
			memset(cnt, 0, sizeof(cnt));
			for (int i=0; i < 5; i++) index[i].clear();

			for (auto i : next) {
				cout << i << endl;
				string s;
				cin >> s;
				// if (kk == 2) {
				// 	fout << "query = " << i << endl;
				// 	fout << s << endl;
				// }
				char c = s[0];
				cnt[c-'A']++;
				index[c-'A'].push_back(i);
			}
			
			for (int i=0; i < 5; i++) {
				if (cnt[i] == value[kk] && left.find(i) != left.end()) {
					idx = i;
					break;
				}
			}
			ans.push_back(idx);
			left.erase(idx);
			// fout << "IDX = " << idx << endl;
			
			// fout << "CNT = {" << cnt[0] << "," << cnt[1] << "," << cnt[2] << "," <<cnt[3] << "," <<cnt[4] << "}" << endl;
		}
		if (left.size() == 1) {
			for (auto v : left) {
				ans.push_back(v);
			}
		}
		//ans.push_back(10 - ss);
		
		string ans_str = "";
		for (int a : ans) {
			ans_str += (char)(a + 'A');
		}
		
		cout << ans_str << endl;
		string res;
		cin >> res;
		if (res[0] == 'N') return 0;
	}
}

 

2번문제까지 풀이하고 나니 8시 10분정도 되어서 곧바로 부리나케 뛰쳐나갔다.

 

3번문제는 대회때는 문제도 읽지 못한 셈. 나중에 3번 문제도 풀어보도록 하겠다.

 

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

 

1700번: 멀티탭 스케줄링

기숙사에서 살고 있는 준규는 한 개의 멀티탭을 이용하고 있다. 준규는 키보드, 헤어드라이기, 핸드폰 충전기, 디지털 카메라 충전기 등 여러 개의 전기용품을 사용하면서 어쩔 수 없이 각종 전기용품의 플러그를 뺐다 꽂았다 하는 불편함을 겪고 있다. 그래서 준규는 자신의 생활 패턴을 분석하여, 자기가 사용하고 있는 전기용품의 사용순서를 알아내었고, 이를 기반으로 플러그를 빼는 횟수를 최소화하는 방법을 고안하여 보다 쾌적한 생활환경을 만들려고 한다. 예를 들어

www.acmicpc.net

컴공 학부 전공과목 운영체제 수업에서 프로세스 스케쥴링을 배울 때, MIN 알고리즘이었나, 이름은 정확하게 기억이 안나는데 그런 알고리즘 이 하나있었다.

 

특징이라고 하면, 지금 멀티탭 스케줄링과 같이, 제한된 리소스에 순차적으로 프로세스들을 실행을 할 때, 멀티탭에서 빼는 것 처럼, 메모리에 로드된 프로세스를 제거하는 횟수를 최소화 하는 알고리즘 중, 실제로 최적이라고 수학적으로 증명된 스케쥴링 기법이다.

 

하지만, 이 알고리즘을 사용하려면 제약 조건이 하나 있는데, 앞으로 미래에 프로세스가 어떤것이 먼저 실행될 것인지를 전부 알아야 한다는 점이다. 따라서 성능 측정에서만 사용한다고 들었다.

 

요놈 알고리즘의 특징은, 프로세스를 제거해야 하는 상황일 때에는, 앞으로 사용되기 까지 가장 오래 걸리는 프로세스를 제거하는 식으로 동작한다. 앞으로 사용되지 않는 프로세스라면 우선순위 1순위로 제거될 것이다.

 

 

#include <bits/stdc++.h>
using namespace std;
vector<int> O[101];
int ptr[101];
vector<int> A;
set<int> tap;
int main() {
	int N, K;
	cin >> N >> K;
	for (int i = 0; i < K; i++) {
		int t;
		cin >> t;
		A.push_back(t);
		O[t].push_back(i);
	}
	int ans = 0;
	for (int i = 0; i < K; i++) {
		auto cur = A[i];
		if (tap.size() >= N && tap.find(cur) == tap.end()) {
			//should remove
			int victim, val;
			victim = -1, val = 0;
			for (auto& t : tap) {
				if (O[t].size() <= ptr[t]) {
					victim = t;
					break;
				}
				if (val < O[t][ptr[t]]) {
					val = O[t][ptr[t]];
					victim = t;
				}
			}
			ans++;
			tap.erase(victim);
		}
		ptr[cur]++;
		tap.insert(cur);
	}
	cout << ans << endl;
	return 0;

}

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

 

1605번: 반복 부분문자열

알파벳 소문자로 이루어진 길이 L인 문자열이 있다. 이 문자열의 부분문자열 중, 적어도 한 번은 반복되는 (다시 말해서, 전체 문자열에서 두 번 이상 나타나는) 부분문자열을 '반복 부분문자열'이라고 부르자. 문자열이 주어지면, 가장 긴 '반복 부분문자열'의 길이를 구하는 프로그램을 작성하시오.

www.acmicpc.net

백준 1605번 문제 - 반복 부분 문자열 입니다.

 

Brute-force로 풀 시에는

모든 부분 문자열을 다 알아내면 O(N^2)이 나올 것이고, 각각을 다 비교하려면 O(N)이 걸리므로

 

총 O(N^3)이 되는데, N=20만이므로, 이 방법으로는 풀 수 없습니다.

 

대충 O(NlogN) 정도 이하의 방법이 필요합니다.

 

여기서 사용할 방법은 바이너리 서치 + 롤링 해시 입니다.

 

원래 문제는 여기서 나타나는 가장 긴 반복 부분 문자열의 길이를 구하는것인데요, 이를

다음 문제로 바꾸어서 봅시다.

 

T(L) => 길이 L인 반복 부분문자열이 존재하는지 아닌지를 확인하는 문제.

 

만약 길이 L인 반복 부분 문자열이 존재한다면 L-1 길이의 반복 부분 문자열도 존재할 것입니다.

간단한 예로 abcdefg 라는 문자열이 두번 나왔고, 이 길이가 7이면

abcdef라는 문자열도 2번 이상 나왔을 것이며, 이 길이는 6입니다.

 

이러한 성질을 이용하면 바이너리 서치를 할 수 있습니다. 즉 T(L)라는 문제를 logL번만 풀면 T(L) == True를 만족하는 최대 L을 구할 수 있지요.

 

반복 부분 문자열의 길이는 최대 L-1이 될 것이고, 최소 0이 될 것입니다.

원문이 a가 10개짜리인 문자열이라면, a가 9개 나온 부분 문자열이 2번 나온것이기 때문이죠.

 

이제 T(L)을 빠른 시간안에 푸는 방법을 생각해봅시다.

 

이는 롤링해시를 이용해서 풀 수 있습니다.

 

문자열 S의 길이가 L이고, 각각의 i번째 문자가 S[i]라고 할 때, 해당 문자열은 다음과 같은 방식으로 해시를 할 수 있습니다.

 

적당한 상수 p 값을 잡고

 

hash(S) = S[1] * p^(L-1) + S[2] * p^(L-2) + ... + S[L-1] * p^1 + S[L] * p^0

 

이때 각각의 S[i]는 영어 소문자이므로 26가지 글자를 가질 수 있으므로, p는 26보다는 큰 적당한 소수를 잡는게 좋겟죠.

저는 29를 사용했습니다.

 

그리고 이 hash 값을 적당한 소수 (저는 10007를 사용했습니다) 로 MOD 연산하여 hash table에 집어 넣습니다.

 

해당 문자열이 나타나는 첫번째 index를 넣어놓고, hash값이 같을 경우, 실제로 같은지 체크를 하는 식으로 롤링 해시를 구성해 보았습니다.

 

매번 저런식으로 hash값을 구하면 시간이 많이 걸리므로, 첫번째만 L만큼의 선형으로 구하고, 두번째 부터는 슬라이딩 윈도우 테크닉을 이용해서 추가되는 글자에 해시값만 추가하고, 떨어져나가는 글자의 해시값만 빼주는 식으로 O(1)로 구해버립니다.

 

아마 요런방식이 문자열 비교 알고리즘인 라빈-카프 알고리즘과 비슷할 것입니다. (저도 그렇다고만 들었고, 아직 라빈-카프 알고리즘을 공부하지 않아서 정확하겐 모르는 상태입니다.)

 

이런식으로 구성하면 해시 충돌이 많이 일어나지 않는다면 대충 O(L)만에 중복되는 문자열이 있는지를 확인할 수 있습니다. 즉 T(L)문제를 풀 수 있죠.

 

다른 사람들 코드를 보니, 이 방식 말고도 풀 수 있는 방법이 더 많은 것으로 보입니다. LCP(Longest Common Prefix) 라던지 등등 문자열과 관련된 다양한 테크닉들을 활용하더군요.

 

일단 제가 풀이한 코드를 첨부하겠습니다.

 

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
char str[200005];
const int p = 29;
const int MOD = 10007;
int Len;
int pexp[200005];

vector<int> hashTable[10007]; //시작 index를 갖는다.
void init();
bool real_match(int s1, int s2, int L) {
    for (int i = 0; i < L; i++) {
        if (str[s1 + i] != str[s2 + i]) {
            return false;
        }
    }    
    return true;
}
bool ok(int L) {
    init();
    //길이 L의 중복 부분문자열이 존재하는지를 리턴
    int hash = 0;
    for (int i = 0; i < L; i++) {
        hash *= p;
        hash += str[i] - 'a';
        hash %= MOD;
    }
    hashTable[hash].push_back(0);
    for (int i = L; i < Len; i++) {
        hash -= (str[i - L] - 'a') * pexp[L - 1] % MOD;
        hash = (hash + MOD) % MOD;
        hash *= p;
        hash += str[i] - 'a';
        hash %= MOD;
        if (hashTable[hash].size()) {
            for (int idx : hashTable[hash]) {
                if (real_match(idx, i - L + 1, L))
                    return true;
            }
        }
        hashTable[hash].push_back(i-L+1);
    }
    return false;
}
void solve() {
    scanf("%s", str);
    int l = 0, r = Len;
    while (l < r) {
        int m = (l + r + 1) / 2;
        if (ok(m)) l = m;
        else r = m - 1;
    }
    cout << l << endl;
}
void pre() {
    pexp[0] = 1;
    for (int i = 1; i <= 200000; i++) {
        pexp[i] = pexp[i - 1] * p % MOD;
    }
}
int main() {
    pre();
    cin >> Len;
    solve();
} 

void init() {
    for (int i = 0; i < MOD; i++) {
        hashTable[i].clear();
    }
}

 

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

 

1006번: 습격자 초라기

하나의 특수 소대로 인접한 두 영역을 커버할 수 있는 배치는 (2,10), (9,16), (4,5), (7,8), (13,14) 이다. 그리고 나머지 6개 구역은 각각 하나의 특수 소대로 커버할 수 있다. 그러므로 최소 11개 특수 소대를 침투시켜야 한다.

www.acmicpc.net

 

백준저지 1006번 문제 습격자 초라기 문제입니다. 악명높은 문제이죠?

1000번 부터 하나씩 문제를 풀려고 하는 PS 입문자를 접게 만드는 문제입니다.

 

다이나믹 프로그래밍으로 풀 수 있는 문제이며, 2xn 타일링, 스티커 문제와 유사한 점이 좀 있습니다.

 

점령해야 하는 공간이 2줄로 이루어져 있고, 최대 2개의 인접한 공간을 같이 점령할 수 있다는 점에 있어서 그렇죠

 

2x1짜리 블럭을 가로, 세로로 놓아서 2줄자리 칸을 채우는 2xn 타일링 문제 (https://www.acmicpc.net/problem/11726)

 

2줄짜리 스티커를 때되, 땔때마다 인접한 스티커는 못쓰게 되는 스티커 문제 (https://www.acmicpc.net/problem/9465)

와 유사합니다.

 

다만 조금 번거로운 부분은, 공간이 환형(동그란 형태)이므로 처음과 끝을 연결시켜줘야 한다는 점입니다.

 

처음과 끝부분이 가로로 겹치게 되면 조금 골치 아파 지죠.

 

그래서 경우를 4가지로 나누어서 각각 처리해줘야 합니다.

 

1) 처음과 끝에 걸치는 경우가 없는 경우

2) 위만 걸치는 경우

3) 아래만 걸치는 경우

4) 위아래 둘다 걸치는 경우

 

각각 걸치는 경우가 성립할 때, 걸치는 부분의 적 병력 수를 W로 바꾼 뒤 문제를 풀면, 다른 곳과는 겹쳐서 점령될 일은 없습니다.

걸치는 부분의 병력 수를 W로 바꾸어서 문제를 푼 뒤, 마지막 부분의 걸치는 부분은 점령하지 않았을 때의 dp 배열을 리턴하면 정답과 같습니다.

 

dp배열 정의는 다음과 같습니다.

 

a[x] => 0~x-1 열까지 모두 점령하고, x번째 열은 위 아래 둘다 점령한 경우

b[x] => 0~x-1 열까지 모두 점령하고, x번째 열은 위 만 점령한 경우

c[x] => 0~x-1 열까지 모두 점령하고, x번째 열은 아래 만 점령한 경우

 

개인적으로 문제 푸는데 코너케이스를 잘 찾지 못해서 매우 고생한 문제입니다.

다음 블로그의 풀이 코드를 조금 참고 했습니다(https://casterian.net/archives/1356)

 

해당 블로그에서는 "a[x] => x-1열까지만 위 아래 모두 점령한 경우" 로 정의하여 b, c DP 배열에 비해서 한칸 뒤까지만 점령한 것으로 정의를 하였는데

 

코너 케이스를 찾다 보니 왜 그렇게 했는지 약간 이해가 갔습니다.

 

제 코드를 보시면, 가로로 두개를 놓는 경우 i == 1인 경우 따로 예외 처리를 해 주었는데, 위 casterian 블로그 주인분이 작성한 코드식으로 정의를 하면 해당 부분을 따로 처리할 필요가 없어집니다.

 

해당 코너 케이스를 찾기 위해서 종만북에서 말하는 스캐폴딩 방법을 활용했습니다.

 

다른곳에서 사용하는 스캐폴딩이라는 단어 뜻은 조금 상이할 수 있으므로 좀더 첨언을 하자면,

랜덤하게 input 값을 생성해서, 정답 소스로 작성한 바이너리와 제가 작성한 오답 바이너리에 각각 input 값을 집어 넣고

output 값을 비교합니다. 

 

다른 output 값이 나올 때 까지 계속 반복하고, 다른 output을 만들어내는 input 파일을 토대로 디버깅을 해서 코너 케이스를 찾아내었습니다.

 

자세한 내용은 코드와 주석들을 확인해주시기 바랍니다.

 

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

int N, W;
int cost[2][10004];
int a[10004], b[10004], c[10004];
void expand() {
	if (cost[0][0] + cost[1][0] <= W) {
		a[0] = b[0] = c[0] = 1;
	}
	else {
		a[0] = 2;
		b[0] = c[0] = 1;
	}
	for (int i = 1; i < N; i++) {
		// i번째 칸에 놓는 방법을 해결한다.
		// i-1까지의 값은 최신임을 보장한다.

		//기존 열에서 두개 그대로 놓는 경우
		a[i] = a[i - 1] + 2;

		//하나만 달랑 놓는 경우
		b[i] = c[i] = a[i - 1] + 1;

		//위로 가로 하나 놓는 경우
		if (cost[0][i] + cost[0][i - 1] <= W) {
			b[i] = min(b[i], c[i - 1] + 1);
		}

		//아래로 가로 하나 놓는 경우
		if (cost[1][i] + cost[1][i - 1] <= W) {
			c[i] = min(c[i], b[i - 1] + 1);
		}

		//하나만 달랑 다시 놓는 경우
		a[i] = min(a[i], b[i] + 1);
		a[i] = min(a[i], c[i] + 1);
		
		//세로로 놓는 경우
		if (cost[0][i] + cost[1][i] <= W) {
			a[i] = min(a[i], a[i - 1] + 1);
		}
		//가로로 두개 놓는 경우
		if (cost[0][i] + cost[0][i - 1] <= W && cost[1][i] + cost[1][i - 1] <= W) {
			if (i >= 2)
				a[i] = min(a[i], a[i - 2] + 2);
			else
				a[i] = min(a[i], 2);
		}
	}

}
void solve() {
	cin >> N >> W;
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < N; j++) {
			cin >> cost[i][j];
		}
	}
	int ans;

	expand();
	//cout << "교차 없는 경우 : ";
	ans = a[N - 1];
	//cout << a[N - 1] << endl;

	if (N > 1) {
		//아래쪽 라인 교차 있는 경우
		if (cost[1][0] + cost[1][N - 1] <= W) { 
			int tmp1 = cost[1][0];
			int tmp2 = cost[1][N - 1];
			cost[1][0] = cost[1][N - 1] = W;
			expand();
			//cout << "아래 교차있는 경우 : ";
			//cout << b[N - 1] << endl;
			ans = min(ans, b[N - 1]);
			cost[1][0] = tmp1;
			cost[1][N - 1] = tmp2;
		}
		if (cost[0][0] + cost[0][N - 1] <= W) {
			int tmp1 = cost[0][0];
			int tmp2 = cost[0][N - 1];
			cost[0][0] = cost[0][N - 1] = W;
			expand();
			//cout << "위 교차 있는 경우 : ";
			//cout << a[N - 1] << endl;
			ans = min(ans, c[N - 1]);
			cost[0][0] = tmp1;
			cost[0][N - 1] = tmp2;
		}
		if (cost[0][0] + cost[0][N - 1] <= W && cost[1][0] + cost[1][N - 1] <= W) {
			int tmp1 = cost[0][0];
			int tmp2 = cost[0][N - 1];
			int tmp3 = cost[1][0];
			int tmp4 = cost[1][N - 1];
			cost[1][0] = cost[1][N - 1] = W;
			cost[0][0] = cost[0][N - 1] = W;
			expand();
			//cout << "위 아래 둘다 교차 있는 경우 : ";
			//cout << a[N - 2] << endl;
			ans = min(ans, a[N - 2]);
			cost[0][0] = tmp1;
			cost[0][N - 1] = tmp2;
			cost[1][0] = tmp3;
			cost[1][N - 1] = tmp4;

		}
	}
	cout << ans << endl;

}
int main() {
	int tc; cin >> tc;
	while (tc--) {
		solve();
	}
}

백준저지 9465번 스티커 문제입니다.

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

 

9465번: 스티커

문제 상근이의 여동생 상냥이는 문방구에서 스티커 2n개를 구매했다. 스티커는 그림 (a)와 같이 2행 n열로 배치되어 있다. 상냥이는 스티커를 이용해 책상을 꾸미려고 한다. 상냥이가 구매한 스티커의 품질은 매우 좋지 않다. 스티커 한 장을 떼면, 그 스티커와 변을 공유하는 스티커는 모두 찢어져서 사용할 수 없게 된다. 즉, 뗀 스티커의 왼쪽, 오른쪽, 위, 아래에 있는 스티커는 사용할 수 없게 된다. 모든 스티커를 붙일 수 없게된 상냥이는 각 스티커에 점

www.acmicpc.net

 

다이나믹 프로그래밍 기초 문제입니다.

 

스티커가 가로 2줄로 이루어져 있고, 하나를 때면 인접한 스티커를 고를 수 없게 됩니다.

최근에 위를 골랏느냐, 아래를 골랐느냐에 따라서 다음 고를 수 있는 상태가 바뀌게 되므로, 2차원 DP 배열로 풀 수 있습니다.

 

 

DP 식은

 

dp[x][status] -> x번째 열 이후 스티커를 다 처리했고, 해당 열의 스티커의 상태가 status가 되었을 때 최대 가치

 

대충 이러한 정의가 될 것입니다.

 

자세한 내용은 코드를 첨부합니다.

 

#include <bits/stdc++.h>
using namespace std;

#define UP      0 //arr에선 위에위치, dp에선 위에것만 선택 가능할 시
#define DOWN    1
#define BOTH    2


int dp[100000][3]; //시작 x좌표와 선택할 수 있는 상태
int arr[100000][2];
int n;
int solve(int len, int stat) {
    if (len < 0 or len >= n or stat < 0 or stat >= 3) return 0;
    if (dp[len][stat] != -1) return dp[len][stat];
    int ans = 0;
    if (stat != DOWN) { //위에걸 선택할 수 있는 상태이면(UP or BOTH)
        ans = max(ans, arr[len][UP] + solve(len+1, DOWN)); //위에걸 선택
        ans = max(ans, solve(len+1, BOTH)); //그냥 안선택
    }
    if (stat != UP) { //아래걸 선택할 수 있는 상태이면(DOWN or BOTH)
        ans = max(ans, arr[len][DOWN] + solve(len+1, UP)); //위에걸 선택
        ans = max(ans, solve(len+1, BOTH)); //그냥 안선택
    }
    return dp[len][stat] = ans;
}
int main() {
    int t;
    cin >> t;
    while(t--) {
        
        cin >> n;
        for (int i=0; i < n; i++) {
            cin >> arr[i][UP];
        }
        for (int i=0; i < n; i++) {
            cin >> arr[i][DOWN];
        }
        memset(dp, -1, sizeof(dp));
        cout << solve(0, BOTH) << '\n';
    }
	return 0;
}

백준저지 1300번 문제 "K번째 수" 문제 풀이입니다.

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

 

1300번: K번째 수

첫째 줄에 배열의 크기 N이 주어진다. N은 105보다 작거나 같은 자연수이다. 둘째 줄에 k가 주어진다. k는 min(109, n2)보다 작거나 같은 자연수이다.

www.acmicpc.net

 

바이너리 서치로 풀이하였습니다.

 

N이 주어졌을 때, 특정 수 x보다 작거나 같은 수의 개수를 O(N)만에 계산할 수 있는데, 이를 이용하여

x보다 작거나 같은 수의 개수가 K와 비슷해질때까지 바이너리 서치를 합니다.

 

x보다 작거나 같은 수의 개수를 num(x) 라 할 때

 

바이너리 서치와 약간 다른 점은, K와 완전히 같은 num(x)가 존재하지 않을 수 있습니다.

 

따라서 K보다 큰 num(x) 중 최소 값을 갖는 x를 구해야 하며, num(x) == num(x+1) == ... == num(x+a) 와 같이 같은 값들을 가지는 num(x)들이 있을 수 있습니다. 이들 중에서는 가장 작은 x값이 정답이 됩니다.

 

num(x) == num(x+1) == ... == num(x+a)와 같은 관계에서는 x는 실제 N*N 매트릭스에 존재하는 값이지만, x+1은 존재하지 않는 값이기 때문입니다.

 

코드는 아래와 같습니다.

 

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll num(ll v, ll N) {
    ll ret = 0;
    //v보다 작거나 같은 원소 개수를 리턴함
    for (int i = 1; i <= N; i++) {
        ret += min(N, v / i);
    }
    return ret;
}
ll solve(ll N, ll K) {
    //binary search 할 예정인데
    // K보다 크거나 같은 값 중 최소 값 구할 예정. (중복 수 있을 수 있으므로)
    // K와 같은 값이면 그중에 가장 작은 값
    ll l = 1, r = N*N;
    while (l < r) {
        ll m = (l + r) / 2;
        long long n = num(m, N);
        if (n == K) {
            ll v = m - 1;
            while (n == num(v, N)) {
                v--;
            }
            r = v + 1;
            break;
        }
        else if (n > K) {
            r = m;
        }
        else if (n < K) {
            l = m + 1;
        }
    }
    return r;
}

int main() {
    ll N, K; cin >> N >> K;
    cout << solve(N, K) << endl;

    return 0;
}

구글 코드잼 2019년도 예선전 후기다. 간단하게 작성해보고자 한다.

 

방금 글 쓰다가 한번 날아가서 조금 대충쓰고 싶어졌다.

 

 

퀄리피케이션 라운드는 절대평가(30점)에다가 27시간동안이나 대회를 하므로 간단하게 풀어버리고

약속에 가려고 했으나, 현실은 약속시간 내내 3번째 문제 솔루션을 생각했다.

 

가볍게 첫번째 문제 Foregone Solution 문제를 보았다. 히든케이스 10^100를 커버하기 위해 익숙하지 않은

파이썬으로 해결했다.

 

모든 4를 3으로 바꾸어 버리는 식으로 코드를 짰다.

 

tc=int(input())

def solve(n):
    a=int(str(n).replace('4', '3'))
    b=n-a
    return str(a) + " " + str(b)
for t in range(1, tc+1):
    n=int(input())
    ans=solve(n)
    print ("Case #" + str(t) + ": " + ans)

 

두번째 문제 You can go your own way

 

대충 문제 읽어보니까 솔루션이 떠올랐는데, 너무 간단해서 함정이 있나 조금 생각했다. E와 S를 서로 바꾸어주면 간단히 해결된다.

요녀석은 빅인티저같은게 필요가 없어서 주력언어인 C++로 작성했다.

 

#include <bits/stdc++.h>
using namespace std;

int main() {
    int tc;
    scanf("%d", &tc);
    for (int t=1; t<=tc; t++) {
        int n; scanf("%d", &n);
        string s;
        cin >> s;
        printf("Case #%d: ", t);
        for (size_t i=0; i < s.length(); i++) {
            if (s[i] == 'S') putchar('E');
            else putchar('S');
        }
        putchar('\n');
    }
	return 0;
}

 

 

세번째 문제, 욕심부리다가 엄청 오랫동안 생각했다.

간단하게 gcd를 구해서 하면 될 거라 생각했는데, 같은 숫자가 나오는 것을 간과했어서  WA를 엄청나게 뿜었다.

파이썬으로 제출한 것은 RE도 자주나왔다. 이유는 아직도 모르겠다..

 

원문이 AAA ABA 형식으로 나오게 되면, 인접한 cipher int value 가 동일한 값이 나오기 때문에 그냥 gcd를 구하면 1이 나오게 된다.

이 점을 유의해야 한다는 것을 까먹었다.

 

Runtime Error 때문에 C++로 솔루션을 작성해 보고, 후에 Python 으로 바꾸어서 히든케이스를 대비했다.

 

사용된 소수들은 인접 값이 다를때만 gcd로 죄다 구하면 되고, 해독할때는 소수 하나만 찾아내서 양옆으로 propagation 시키면 됬던 풀이이다.

 

일단 씨쁠쁠 풀이이다.

 

#include <bits/stdc++.h>
using namespace std;

int N, L;  
int plainValue[105];

int main() {
    int tc; cin >> tc;
    for (int t=1; t<=tc; t++) {
        memset(plainValue, 0, sizeof(plainValue));
        cin >> N >> L;
        vector<int> ciphers;
        set<int> primes;
        map<int, char> int2char;
        map<char, int> char2int;
        ciphers.clear();
        primes.clear();
        int2char.clear();
        char2int.clear();
        for (int i=0; i < L; i++) {
            int tmp; cin >> tmp;
            ciphers.push_back(tmp);
        }
        for (int i=0; i< L-1; i++) {
            if (ciphers[i] != ciphers[i+1]) {
                int cd = __gcd(ciphers[i], ciphers[i+1]);
                primes.insert(cd);
                primes.insert(ciphers[i]/cd);
                primes.insert(ciphers[i+1]/cd);
            }
        }
        char c = 'A';
        for (auto& p : primes) {
            if (int2char.find(p) == int2char.end()) {
                char2int[c] = p;
                int2char[p] = c++;
            }
        }
        
        int idx, pval, ppval;
        for (idx=0; idx < L-1; idx++) {
            if (ciphers[idx] != ciphers[idx+1]) {
                int cd = __gcd(ciphers[idx], ciphers[idx+1]);
                plainValue[idx+1] = cd;
                pval = ciphers[idx+1] / cd;
                ppval = ciphers[idx] / cd;
                break;
            }
        }
        
        
        for (int i=idx; i >= 1; i--) {
            plainValue[i] = ppval;
            ppval = ciphers[i-1] / ppval;
        }
        plainValue[0] = ppval;
        
        for (int i=idx+2; i < L; i++) {
            plainValue[i] = pval;
            pval = ciphers[i] / pval;
        }
        plainValue[L] = pval;
        
        printf("Case #%d: ", t);
        for (int i=0; i <= L; i++) {
            putchar(int2char[plainValue[i]]);
        }
        putchar('\n');
    }
}

 

이렇게 짠 녀석을 그대로 파이썬으로 옮기면 히든케이스도 커버할 수 있다.

 

def gcd(x,y):
    while(y):
        x,y=y,x%y
    return x
    
def make_unique(l):
    t = []
    for v in l:
        if len(t)==0 or t[-1] != v:
            t.append(v)
    return t
   
tc=int(input())
for t in range(1, tc+1):
    plainValue = [0 for i in range(0, 110)]
    N, L = map(int, input().split(' '))
    ciphers = list(map(int, input().split(' ')))
    primes = []
    int2char = {}
    
    for i in range(0, L-1):
        if ciphers[i] != ciphers[i+1]:
            cd = gcd(ciphers[i], ciphers[i+1])
            primes.append(cd)
            primes.append(ciphers[i]//cd)
            primes.append(ciphers[i+1]//cd)
            
    primes.sort()
    primes = make_unique(primes)
    c = 'A'
    for p in primes:
        if int2char.get(p) == None:
            int2char[p] = c
            c = chr(ord(c) + 1)
            
    idx = 0
    pval = 0
    ppval = 0
    
    for idx in range(0, L-1):
        if ciphers[idx] != ciphers[idx+1]:
            cd = gcd(ciphers[idx], ciphers[idx+1])
            plainValue[idx+1] = cd
            pval = ciphers[idx+1] // cd
            ppval = ciphers[idx] // cd
            break
    
    for i in range(idx, 0, -1):
        plainValue[i] = ppval
        ppval = ciphers[i-1] // ppval
    plainValue[0] = ppval
    
    for i in range(idx+2, L):
        plainValue[i] = pval
        pval = ciphers[i]  // pval
    plainValue[L] = pval
    
    
    ans = ""
    for i in range(0, L+1):
        ans += int2char[plainValue[i]]
    print ("Case #" + str(t) + ": " + ans)

 

마지막 문제의 경우는 아직 풀지를 못했는데, 나중에 여유가 되면 한번 풀어보고 올려보도록 하겠다.

백준 17135번 문제 캐슬 디펜스 문제 풀이입니다.

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

 

17135번: 캐슬 디펜스

첫째 줄에 격자판 행의 수 N, 열의 수 M, 궁수의 공격 거리 제한 D가 주어진다. 둘째 줄부터 N개의 줄에는 격자판의 상태가 주어진다. 0은 빈 칸, 1은 적이 있는 칸이다.

www.acmicpc.net

N, M, D값이 크지 않으므로, 하나하나 다 따라가는 시뮬레이션 방식으로 풀이하면 됩니다.

 

test(a,b,c) 함수는 궁수의 위치가 각각 (N, a) (N, b) (N, c)일때 최대로 잡을 수 있는 적의 수를 리턴합니다.

 

choose_turn 함수는 각각의 턴마다 각각의 궁수가 공격할 적의 위치를 set에 추가합니다.

 

 

#include <bits/stdc++.h>
using namespace std;

int N, M, D;
int field[16][16];
typedef struct _Pos {
    int x, y;
    bool operator<(const struct _Pos& rhs) const {
        return x != rhs.x ? x < rhs.x : y < rhs.y;
    }
} Pos;
inline int dist(int x1, int y1, int x2, int y2) {
    return abs(x1-x2) + abs(y1-y2);
}
inline int dist(Pos p1, Pos p2) {
    return abs(p1.x - p2.x) + abs(p1.y - p2.y);
}
void choose_target(int mmap[][16], set<Pos>& kset, int turn, int archer_pos) {
    Pos archer = { N, archer_pos };
    Pos vic_pos = { 1000, 1000 };
    int pd = dist(archer, vic_pos); //previous dist
    for (int i=0; i< N - turn; i++) {
        for (int j=0; j < M; j++) {
            if (mmap[i][j] == 0) continue;
            Pos epos = { i + turn, j };
            int d = dist(archer, epos);
            if (d <= D) {
                if (d < pd || (d == pd && epos.y < vic_pos.y)) {
                    pd = d;
                    vic_pos = epos;
                }
            }
        }
    }
    if (vic_pos.x != 1000) kset.insert({ vic_pos.x - turn, vic_pos.y });
}
int test(int a, int b, int c) {
    //archer pos = (N, a) (N, b) (N, c)
    int kill = 0; //ret val
    int mmap[16][16];
    for (int i=0; i < N; i++)
        for (int j=0; j < M; j++)
            mmap[i][j] = field[i][j];
    
    set<Pos> kill_set;
    kill_set.clear();
    for (int t=0; t < N; t++) {
        //for each turn
        choose_target(mmap, kill_set, t, a);
        choose_target(mmap, kill_set, t, b);
        choose_target(mmap, kill_set, t, c);
        kill += (int)kill_set.size();
        for (auto& v : kill_set) {
            mmap[v.x][v.y] = 0;
        }
        kill_set.clear();
    }
    return kill;
}
int main() {
    cin >> N >> M >> D;
    for (int i=0; i < N; i++)
        for (int j=0; j < M; j++)
            cin >> field[i][j];
    
    int ans = 0;
    for (int i=0; i < M-2; i++) 
        for (int j=i+1; j < M-1; j++)
            for (int k=j+1; k < M; k++)
                ans = max(ans, test(i,j,k));
            
    cout << ans << endl;
	return 0;
}


+ Recent posts