※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28073번 문제인 PSAT 특별과정이다.
문제는 아래 링크를 확인하자.

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

 

28073번: PSAT 특별과정

첫 번째 줄에 문제의 개수 $N$ $(2 \le N \le 1\,000\,000)$과 문제의 연결 관계의 수 $M$ $(0 \le M \le \min(1\,000\,000,\cfrac{N(N-1)}{2}))$이 공백으로 구분되어 주어진다. 각 문제는 1번부터 차례대로 $N$번까지 번

www.acmicpc.net

가장 적은 '문제'를 푸는 경로는 BFS를 이용해서 간단하게 구해낼 수 있다. 이 중 사전순으로 가장 빠른 경로를 찾으려면 어떻게 해야할까?

 

모든 '문제'가 갖는 가중치가 서로 다르다면 bfs 과정에서 각 노드에서 다음 노드를 큐(queue)에 담을 때 가중치가 증가하는 순서대로 담는 것으로 문제를 해결할 수 있을 것이다. 이와 같이 탐색하면 탐색 과정에서 나오는 같은 길이의 경로들끼리 비교했을 때 먼저 나오는 경로가 사전순으로 앞서게 되기 때문이다.

 

그러나 이 문제에서는 각 '문제'가 갖는 가중치에 중복이 있으므로 이에 유의해야 한다. 문자 S를 출력하게 되는 문제와 연결되어 있는 가중치가 같은 서로 다른 두 노드 A1과 A2, 그리고 가중치가 B>C인 두 노드 B와 C가 있는 경우를 생각해보자. 탐색과정에서 A1을 SA2보다 먼저 방문하고 A1에서 다음으로 탐색할 수 있는 후보가 B, A2에서 다음으로 탐색할 수 있는 후보가 C뿐이라면 위와 같은 방식으로 탐색을 하면 SA1B가 SA2C보다 먼저 등장하게 된다. 그러나 SA2C가 SA1B보다 사전순으로 앞서므로 이 방법을 수정하지 않으면 문제의 답을 올바르게 낼 수 없다.

 

당장은 같은 가중치를 가져 사전순에 영향을 주지 않지만 이후의 탐색 과정에서 가중치가 역전되어 사전순에 영향을 줄 수 있는 경우가 문제이므로, 이를 해결하기 위해 BFS 탐색 중 현재 노드에서 방문할 수 있는 중복된 가중치를 갖는 노드가 존재하는 경우를 만날 때마다 해당 노드들을 합쳐 새로운 노드를 만들어주자. 이와 같이 구현하면 위와 같은 문제를 피해 올바른 답을 구해낼 수 있다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <queue>
#include <set>
using namespace std;

int N, M; char S, E;
vector<int> G[1000002];
int prv[1000002];
string L;

bool comp(int& x, int& y) {
	return L[x] < L[y];
}

queue<int> que;
void bfs() {
	que.push(N + 1); prv[N + 1] = -1;
	for (int i = 1; i <= N; i++) {
		if (L[i] == S) G[N + 1].emplace_back(i);
	}

	for (int i = 0; i <= N; i++) sort(G[i].begin(), G[i].end(), comp);

	while (!que.empty()) {
		int cur = que.front(); que.pop();
		if (L[cur] == E) {
			string ans = "";
			while (prv[cur] > 0) {
				ans += L[cur];
				cur = prv[cur];
			}

			while (!ans.empty()) {
				cout << ans.back();
				ans.pop_back();
			}
			return;
		}
		int old = 0; char oldchar = ' '; set<int> st;
		for (auto& nxt : G[cur]) {
			if (prv[nxt]) continue;
			prv[nxt] = cur;
			if (L[nxt] != oldchar) {
				if (old) {
					set<int> stst;
					for (auto& x : st) {
						for (auto& xx : G[x]) {
							stst.insert(xx);
						}
					}
					G[old].clear();
					for (auto& xxx : stst) G[old].emplace_back(xxx);
					sort(G[old].begin(), G[old].end(), comp);
					st.clear();
					que.push(old);
				}
				old = nxt, oldchar = L[nxt];
			}

			st.insert(nxt);
		}

		if (old) {
			set<int> stst;
			for (auto& x : st) {
				for (auto& xx : G[x]) {
					stst.insert(xx);
				}
			}
			G[old].clear();
			for (auto& xxx : stst) G[old].emplace_back(xxx);
			sort(G[old].begin(), G[old].end(), comp);
			st.clear();
			que.push(old);
		}
	}

	cout << "Aaak!";
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N >> M >> S >> E >> L;
	L = " " + L + "*";
	while (M--) {
		int x, y; cin >> x >> y;
		G[x].emplace_back(y);
		G[y].emplace_back(x);
	}
	
	bfs();
}
728x90

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28072번 문제인 K512에서 피자 먹기이다.
문제는 아래 링크를 확인하자.

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

 

28072번: $K$512에서 피자 먹기

첫 번째 줄에 피자 조각의 개수 $N$ $(1 \le N \le 1\,000\,000)$과 마법의 수 $K$ $(1 \le K \le 100\,000)$가 주어진다. 두 번째 줄에 특정 조각부터 시작해 시계 방향으로 각 조각의 무게를 나타내는 정수 $a_1, a

www.acmicpc.net

psum[i]를 1번부터 i번까지의 피자조각의 무게의 합을 K로 나눈 나머지라 정의하자. 이 배열은 i를 1부터 N까지 차례대로 늘려나가면서 psum[i-1]의 값을 참조해(psum[0]=0으로 정의하자) O(N)으로 계산할 수 있다.

 

L번 조각부터 R번 조각까지로 이루어진 피자 조각을 [L,R]이라 부르자. 이 때, 피자 조각 [L,R]의 무게가 K의 배수라면 psum[L-1]과 psum[R]의 값이 같아야 함을 관찰할 수 있다. 또한, psum[x]와 psum[y]의 값이 같다면 피자 조각 [x+1,y] 및 [y+1,x]의 무게 또한 K의 배수가 됨을 관찰할 수 있다.

 

위의 관찰을 이용하면 문제의 답은 가능한 k값에 대하여 psum의 값이 k이고 1 이상 N 이하인 i값의 개수의 최댓값이 됨을 알 수 있다. 이를 구하는 코드를 작성해 문제를 해결하자.

 

아래는 제출한 소스코드이다.

#include <iostream>
using namespace std;

int N, K;
int arr[1000001];
int cnt[100001];

int ans;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N >> K;
	for (int i = 1; i <= N; i++) {
		cin >> arr[i];
		arr[i] += arr[i - 1];
		arr[i] %= K;
		cnt[arr[i]]++;
	}

	for (int i = 0; i < K; i++) ans = max(ans, cnt[i]);

	cout << ans;
}
728x90

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28071번 문제인 승형이의 사탕 사기이다.
문제는 아래 링크를 확인하자.

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

 

28071번: 승형이의 사탕 사기

첫째 줄에 $N, M, K$가 주어진다. (1N,M,K300) 둘째 줄에 각각의 사탕 상자에 담겨있는 사탕의 개수 $a_1,a_2, \cdots, a_N$가 주어진다. (1ai300) 입력으로 주어지는 모든 수는 정수

www.acmicpc.net

N종류의 사탕상자를 차례대로 읽어나갈 때, dp[m][k]를 이전까지 읽은 사탕상자들을 총 m개 사용해 얻을 수 있는 사탕의 개수 중 K로 나눈 나머지가 k인 값의 최댓값으로 정의하자.

 

이 때, 새로운 사탕상자의 크기 n을 읽어들였을 때, dp[m][k]를 m이 작은 순서대로 찾아가 각 사탕 개수에 n개의 사탕을 추가해 만들 수 있는 새로운 사탕개수의 값을 이용해 dp[m+1][k'](k'은 새 사탕개수를 K로 나눈 나머지)의 값을 갱신해나가는 것으로 dp배열을 올바르게 갱신해나갈 수 있다. 각 상태에 여러 사탕상자가 아닌 하나의 사탕상자만을 추가해보는 것으로 충분한 이유는 이후에 dp[m+1][k'] 상태에 대하여 다시 사탕상자를 하나 추가해보는 것을 시도하기 때문이다.

 

아래는 제출한 소스코드이다.

#include <iostream>
using namespace std;

int N, M, K;
int dp[301][301]; // dp[상자m개][K로 나눈 나머지]: 해당 조건 만족하는 사탕 최댓값

int ans;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N >> M >> K;
	for (int m = 0; m <= M; m++) {
		for (int k = 0; k < K; k++) {
			dp[m][k] = -1000000007;
		}
	}
	dp[0][0] = 0;

	while (N--) {
		int x; cin >> x;
		for (int m = 0; m < M; m++) {
			for (int k = 0; k < K; k++) {
				int tmp = dp[m][k] + x;
				dp[m + 1][tmp % K] = max(dp[m + 1][tmp % K], tmp);
			}
		}
	}

	for (int m = 1; m <= M; m++) ans = max(ans, dp[m][0]);

	cout << ans;
}
728x90

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28070번 문제인 유니의 편지 쓰기이다.
문제는 아래 링크를 확인하자.

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

 

28070번: 유니의 편지 쓰기

유니가 편지를 써야 할 시기를 YYYY-MM 형식으로 출력한다. 편지를 써야 할 시기가 여러 개일 경우, 가장 앞선 시기를 출력한다.

www.acmicpc.net

현재 군대에 있는 친구의 수를 나타내는 변수를 하나 준비하자. 이러한 변수를 이용하면 친구들의 입대시기 s1과 전역시기 s2를 시간순으로 정렬한 다음 시간순으로 살피며 입대와 전역이 일어날 때마다 해당 변수의 값을 바꾸는 것으로 매달 입대한 친구의 수를 알 수 있다. 이를 이용해 가장 많은 친구가 입대해있는 달을 찾아 문제를 해결하자.

 

같은 달에 입대하는 친구와 전역해서 더이상 군인이 아니게 된 친구가 있는 경우 전역하는 친구를 먼저 처리해 해당 달에 입대한 친구의 수 값이 부풀려지는 순간이 나오지 않게끔 변수를 관리할 수 있다.

 

입력으로 주어진 YYYY-MM꼴의 문자열은 문자열 자체 기본 대소비교를 통해서도 정렬이 가능하므로 특별한 문자열처리를 하지 않더라도 문제를 충분히 해결할 수 있다!

 

아래는 제출한 소스코드이다.

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

int N;
vector<pair<string, int>> vec;
int val, mx; string ans;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N;
	while (N--) {
		string s1, s2; cin >> s1 >> s2;
		vec.emplace_back(make_pair(s1, -1));
		vec.emplace_back(make_pair(s2, 1));
	}

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

	for (auto& p : vec) {
		val -= p.second;
		if (mx < val) mx = val, ans = p.first;
	}

	cout << ans;
}

 

728x90

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28069번 문제인 김밥천국의 계단이다.
문제는 아래 링크를 확인하자.

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

 

28069번: 김밥천국의 계단

첫 번째 줄에 계단 개수에 해당하는 $N$, 계단을 오르는 횟수 $K$가 주어진다. $(1 \leq N, K \leq 1\,000\,000)$

www.acmicpc.net

0번 계단에서 각 n번째 계단까지 올라가는 데에 x번 계단 오르기로 올라갈 수 있다면 x보다 큰 횟수 y로도 올라갈 수 있음을 관찰하자. 이는 0번 계단에서 2번 행동을 x와 y의 차만큼 반복하고 x번 계단 오르기를 하는 것으로 가능하다.

 

위의 관찰을 이용하면 N번 계단까지 필요한 최소 계단 오르기 횟수를 구해 그 값이 K보다 작거나 같은지를 판단하는 것으로 문제를 해결할 수 있다. 최소 계단 오르기 횟수는 각 계단을 노드로, 각 계단에서 이동할 수 있는 계단의 관계를 방향 에지로 갖는 그래프 모델링에서의 BFS를 이용해 구할 수 있다.

 

아래는 제출한 소스코드이다.

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

int N, K;
int dist[1000001];

queue<int> que;
void bfs() {
	dist[0] = 1;
	que.push(0);
	while (!que.empty()) {
		int cur = que.front(); que.pop();
		int nxt1 = cur + 1, nxt2 = cur + cur / 2;
		if (nxt1 <= 1000001 && !dist[nxt1]) {
			dist[nxt1] = dist[cur] + 1;
			que.push(nxt1);
		}
		if (nxt2 <= 1000001 && !dist[nxt2]) {
			dist[nxt2] = dist[cur] + 1;
			que.push(nxt2);
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	bfs();

	cin >> N >> K;

	if (dist[N] - 1 <= K) cout << "minigimbob";
	else cout << "water";
}
728x90

※ 글쓴이는 취미로 코딩을 익혀보는 사람이라 정확하지 않은 내용을 담고 있을 수 있다 ※

 

이번에 볼 문제는 백준 28068번 문제인 I Am Knowledge이다.
문제는 아래 링크를 확인하자.

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

 

28068번: I Am Knowledge

저택에 살고 있는 마법사는 지하의 도서관에 자주 방문한다. 어느 날, 마법사는 도서관에 있는 책 N권을 모두 읽기로 했다. 책은 한 번에 한 권씩만 읽을 수 있지만, 책을 읽는 순서는 마음대

www.acmicpc.net

 

각 책을 (a,b)와 같이 나타낸다면, 해당 책은 마법사의 즐거움이 a 이상일 때 읽어 즐거움을 b-a만큼 증가시켜줄 수 있다. (단, b-a가 음수이면 a-b만큼 감소한다.) 모든 책을 읽는 방법이 존재한다면 순서를 적절히 조절해 '책을 읽기 전과 비교했을 때 읽은 뒤 즐거움이 증가하는 책'들을 모두 읽고 그렇지 않은 책들을 마저 읽어 모든 책을 읽을 수도 있음을 관찰하자.

 

읽은 뒤 즐거움이 증가하는 책들을 조건을 만족하도록 전부 읽는 것이 가능한지는 그러한 책들을 a값을 기준으로 오름차순 정렬했을 때 이 책들을 그 순서대로 읽어나갈 수 있는지를 확인하는 것으로 해낼 수 있다.

 

읽은 뒤 즐거움이 감소하는 책들을 차례대로 읽을 수 있는지를 생각해보자. 만약 그러한 방법이 존재한다면 모든 책을 다 읽었을 때의 최종 즐거움 값은 (모든 b의 값의 합) - (모든 a의 값의 합)이 될 것이다. 이 즐거움 값에서 시작해 즐거움이 감소하는 책을 읽어나가던 과정을 거꾸로 실행해나가는 것은 '현재 즐거움 값이 b 이상일 때 읽어 즐거움을 a-b(>0)만큼 증가시키는' 책을 읽어나가는 것으로 생각할 수 있다. 이것이 앞선 문단의 경우와 동일함을 관찰하면 이 또한 같은 방법으로 해결할 수 있음을 알 수 있다.

 

아래는 제출한 소스코드이다.

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;

int N;
vector<pair<int, int>> vec1, vec2;
ll val1, val2;

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	cin >> N;
	while (N--) {
		int a, b; cin >> a >> b;
		val1 += (b - a);
		if (a > b) vec1.emplace_back(make_pair(b, a - b));
		else vec2.emplace_back(make_pair(a, b - a));
	}

	sort(vec1.begin(), vec1.end());
	sort(vec2.begin(), vec2.end());

	for (auto& p : vec1) {
		if (val1 < p.first) {
			cout << 0;
			return 0;
		}
		val1 += p.second;
	}

	for (auto& p : vec2) {
		if (val2 < p.first) {
			cout << 0;
			return 0;
		}
		val2 += p.second;
	}

	cout << 1;
}
728x90

+ Recent posts