문제

 

입력

 

출력

 

 


 

 

문제 해결

반례에 주의하자!!

R : 배열을 reverse하는 연산

D : 배열의 앞 원소를 pop하는 연산

R이 홀수개이면 마지막에 한 번만 reverse 연산을 하여 시간 복잡도를 개선한다. 대신 flag를 세워 앞에서 pop 할 건지, 뒤에서 pop 할 건지 체크해야 한다.

 

반례는 크게 세 가지가 있는데, 코드 블럭 내에 적혀있다.

 

 

코드

#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

int t, n, z, res;
string s1, s2;
list<int> v;

/*
* 반례
* 1. 빈 배열에서 pop하는 경우 : error 출력
* 2. 빈 배열에서 R하는 경우 : [] 출력
* 3. 모든 함수 수행 후 빈 배열이 되는 경우 : [] 출력
*/
string input() {

	int sum = 0;
	
	cin >> s1 >> n >> s2;

	for (int i = 1; i < s2.size(); i++) {

		if (s2[i] >= '0' && s2[i] <= '9')
			sum = 10 * sum + (int)(s2[i] - 48);		// '0' = 48, '9' = 57
		else if (s2[i] == ',' || s2[i] == ']') {
			if (!n) break;
			v.push_back(sum);
			sum = 0;
		}
	}
		
	return s1;
}

int solution(string s1) {	// error : 0, not error : 1, empty array : 2

	int s = s1.size();
	int c = 1, cnt = 0;
	bool flag = true;		// true일 때는 pop_front, false일 때는 pop_back

	for (int i = 0; i < s; i++) {
		if (s1[i] == 'R') {
			if (!v.size()) c = 2;	// 2. 빈 배열에서 R하는 경우 : [] 출력
			flag = !flag;
			cnt++;
		}
		else if (s1[i] == 'D') {
			if (!v.size()) return 0;	// 1. 빈 배열에서 pop하는 경우 : error 출력
			flag ? v.pop_front() : v.pop_back();
		
		}
	}

	if (cnt % 2) reverse(v.begin(), v.end());	// R 연산이 홀수개인 경우 마지막에 한 번만 reverse
	if (!v.size()) c = 2;			// 3. 모든 함수 수행 후 빈 배열이 되는 경우 : [] 출력
	return c;
}


int main() {

	cin >> t;

	while (t--) {
		v.clear();
		res = solution(input());
		int z = v.size();

		if (!res) {
			cout << "error" << '\n';
			continue;
		}
		else if (res == 2) {
			cout << "[]" << '\n';
			continue;
		}

		cout << '[';
		list<int>::iterator iter = v.begin();
		for (int i = 0; i < z - 1; i++) {
			cout << *iter << ',';
			iter++;
		}
		cout << *iter << ']' << '\n';

	}
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 4256 : 트리  (0) 2023.08.06
[C++] 백준 20922 : 겹치는 건 싫어  (0) 2022.02.09
[C++] 백준 1072 : 게임  (0) 2022.02.08
[C++] 백준 2533 : 사회망 서비스(SNS)  (0) 2022.02.07

 

 

 

문제

 

입력

 

출력


 

 

문제 해결

 

pl, pr : 전위 순회의 첫 번째와 마지막

il, ir : 중위 순회의 첫 번째와 마지막

 

전위 순회에서 첫 번째는 무조건 현재 트리의 루트.

중위 순회에서 현재 트리 루트의 위치를 찾으면 그 왼쪽 pl까지의 개수는 왼쪽 자식트리 노드의 개수

같은 이유로 루트의 오른쪽 pr까지의 개수는 오른쪽 자식트리 노드의 개수

 

 

 

코드

#include <iostream>

using namespace std;

int pre[1001], in[1001];

// 전위 순회의 루트는 무조건 첫 번째
// 중위 순회에서 루트의 왼쪽 pl까지의 개수 = 왼쪽 자식트리 노드 개수
// 중위 순회에서 루트의 오른쪽 pr까지의 개수 = 오른쪽 자식 트리 노드 개수

void solution(int pl, int pr, int il, int ir) {

	if (pl == pr) {
		cout << pre[pl] << ' ';
		return;
	}

	for (int i = 0; i <= pr - pl; i++) {
		if (pre[pl] == in[il + i]) {
			solution(pl + 1, pl + i, il, il + i - 1);		// 왼쪽 트리
			solution(pl + i + 1, pr, il + i + 1, ir);		// 오른쪽 트리
			cout << pre[pl] << ' ';							// 루트 출력
		}
	}

}

int main() {
	int t, n;
	cin >> t;

	while (t--) {
		cin >> n;
		for (int i = 0; i < n; i++)
			cin >> pre[i];
		for (int i = 0; i < n; i++)
			cin >> in[i];

		solution(0, n - 1, 0, n - 1);
		cout << endl;
	}
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 5430 : AC  (0) 2023.08.06
[C++] 백준 20922 : 겹치는 건 싫어  (0) 2022.02.09
[C++] 백준 1072 : 게임  (0) 2022.02.08
[C++] 백준 2533 : 사회망 서비스(SNS)  (0) 2022.02.07

 

 

 

문제

시골에 있는 태양이의 삼촌 댁에는 커다란 참외밭이 있다. 문득 태양이는 이 밭에서 자라는 참외가 도대체 몇 개나 되는지 궁금해졌다. 어떻게 알아낼 수 있는지 골똘히 생각하다가 드디어 좋은 아이디어가 떠올랐다. 유레카! 1m2의 넓이에 자라는 참외 개수를 헤아린 다음, 참외밭의 넓이를 구하면 비례식을 이용하여 참외의 총개수를 구할 수 있다.

1m2의 넓이에 자라는 참외의 개수는 헤아렸고, 이제 참외밭의 넓이만 구하면 된다. 참외밭은 ㄱ-자 모양이거나 ㄱ-자를 90도, 180도, 270도 회전한 모양(┏, ┗, ┛ 모양)의 육각형이다. 다행히도 밭의 경계(육각형의 변)는 모두 동서 방향이거나 남북 방향이었다. 밭의 한 모퉁이에서 출발하여 밭의 둘레를 돌면서 밭경계 길이를 모두 측정하였다.

예를 들어 참외밭이 위 그림과 같은 모양이라고 하자. 그림에서 오른쪽은 동쪽, 왼쪽은 서쪽, 아래쪽은 남쪽, 위쪽은 북쪽이다. 이 그림의 왼쪽위 꼭짓점에서 출발하여, 반시계방향으로 남쪽으로 30m, 동쪽으로 60m, 남쪽으로 20m, 동쪽으로 100m, 북쪽으로 50m, 서쪽으로 160m 이동하면 다시 출발점으로 되돌아가게 된다.

위 그림의 참외밭  면적은 6800m2이다. 만약 1m2의 넓이에 자라는 참외의 개수가 7이라면, 이 밭에서 자라는 참외의 개수는 47600으로 계산된다.

1m2의 넓이에 자라는 참외의 개수와, 참외밭을 이루는 육각형의 임의의 한 꼭짓점에서 출발하여 반시계방향으로 둘레를 돌면서 지나는 변의 방향과 길이가 순서대로 주어진다. 이 참외밭에서 자라는 참외의 수를 구하는 프로그램을 작성하시오.

 

 

입력

첫 번째 줄에 1m2의 넓이에 자라는 참외의 개수를 나타내는 양의 정수 K (1 ≤ K ≤ 20)가 주어진다. 참외밭을 나타내는 육각형의 임의의 한 꼭짓점에서 출발하여 반시계방향으로 둘레를 돌면서 지나는 변의 방향과 길이 (1 이상 500 이하의 정수) 가 둘째 줄부터 일곱 번째 줄까지 한 줄에 하나씩 순서대로 주어진다. 변의 방향에서 동쪽은 1, 서쪽은 2, 남쪽은 3, 북쪽은 4로 나타낸다.

 

 

출력

첫째 줄에 입력으로 주어진 밭에서 자라는 참외의 수를 출력한다.

 

 

 


 

문제 해결

문제에서 구하려는 도형의 넓이는 도형을 직사각형이라고 가정한 넓이에서 움푹 파인 넓이를 빼면 됩니다.

 

변의 입력은 가로, 세로가 번갈아가며 주어지므로 현재 변과 다음 변을 곱한 값 중 최대값을 maxArea에 저장합니다. 최대값이 갱신될 때마다 현재 변으로부터 "3번째 변과 4번째 변의 곱"을 maxArea에서 뺀 값을 res에 저장합니다. 입력되는 도형 구조 상 최대값을 나타내는 변으로부터 3번째 변과 4번째 변이 무조건 움푹 파인 부분이 되기 때문입니다.

 

 

 

코드

#include <iostream>

using namespace std;

pair<int, int> ary[6];
int n, maxArea = 1, cur, res;

int main(){
    
    cin >> n;

    for (int i = 0; i < 6; i++)
        cin >> ary[i].first >> ary[i].second;

    for (int i = 0; i < 6; i++){
        cur = ary[i].second * ary[(i + 1) % 6].second;

        if (cur > maxArea){
            maxArea = cur;
            res = maxArea - ary[(i + 3) % 6].second * ary[(i + 4) % 6].second;
        }
    }

    cout << res * n;
}

'🎲 BOJ > 🥈' 카테고리의 다른 글

[C++] 백준 2056 : 작업  (0) 2022.02.10
[C++] 백준 10826 : 피보나치 수 4  (0) 2022.02.06
[C++] 백준 1181 : 단어 정렬  (0) 2022.02.04
[C++] 백준 9084 : 동전  (0) 2022.02.03

 

 

문제

준원이는 저번 주에 살면서 처음으로 코스트코를 가 봤다. 정말 멋졌다. 그런데, 몇 개 담지도 않았는데 수상하게 높은 금액이 나오는 것이다! 준원이는 영수증을 보면서 정확하게 계산된 것이 맞는지 확인해보려 한다.

영수증에 적힌,

  • 구매한 각 물건의 가격과 개수
  • 구매한 물건들의 총 금액

을 보고, 구매한 물건의 가격과 개수로 계산한 총 금액이 영수증에 적힌 총 금액과 일치하는지 검사해보자.

 

 

입력

첫째 줄에는 영수증에 적힌 총 금액 가 주어진다.

둘째 줄에는 영수증에 적힌 구매한 물건의 종류의 수 이 주어진다.

이후 개의 줄에는 각 물건의 가격 와 개수 가 공백을 사이에 두고 주어진다.

 

 

출력

 

구매한 물건의 가격과 개수로 계산한 총 금액이 영수증에 적힌 총 금액과 일치하면 Yes를 출력한다. 일치하지 않는다면 No를 출력한다.

 

 


 

코드

 

#include <iostream>

using namespace std;

int total, n, price, m;

int main() {

	cin >> total >> n;

	for (int i = 0; i < n; i++) {
		cin >> price >> m;
		total -= price * m;
	}

	if (!total) cout << "Yes";
	else cout << "No";
}

 

'🎲 BOJ > 🥉' 카테고리의 다른 글

[C++] 백준 10824 : 네 수  (0) 2022.08.08
[C++] 백준 13305 : 주유소  (0) 2022.01.27

 

 

 

문제

네 자연수 A, B, C, D가 주어진다. 이때, A와 B를 붙인 수와 C와 D를 붙인 수의 합을 구하는 프로그램을 작성하시오.

두 수 A와 B를 합치는 것은 A의 뒤에 B를 붙이는 것을 의미한다. 즉, 20과 30을 붙이면 2030이 된다.

 

 

입력

첫째 줄에 네 자연수 A, B, C, D가 주어진다. (1 ≤ A, B, C, D ≤ 1,000,000)

 

출력

A와 B를 붙인 수와 C와 D를 붙인 수의 합을 출력한다.

 

 


 

 

문제 해결

머리가 복잡해서 식힐겸 오랜만에 아주아주 간단한 문제를 풀어봤다. string을 이용하면 한 줄이면 끝나지만 일부러 돌아왔다. ( string 사용 시 : cout << stol(a + b) + stol(c + d); )

 

b의 자릿수만큼 a를, d의 자릿수만큼 c를 늘리면 된다.

그런 다음 a에 b를 더한 수와 c에 d를 더한 수를 더해주면 구하는 결과가 나온다.

 

 

 

코드

#include <iostream>
using namespace std;

long long a, b, c, d;

int main() {

	cin >> a >> b >> c >> d;

	int tmp = b;
	while (tmp) {
		a *= 10;
		tmp /= 10;
	}
	a += b;

	tmp = d;
	while (tmp) {
		c *= 10;
		tmp /= 10;
	}
	c += d;

	cout << a + c;
}

'🎲 BOJ > 🥉' 카테고리의 다른 글

[C++] 백준 25304 : 영수증  (0) 2023.02.01
[C++] 백준 13305 : 주유소  (0) 2022.01.27

 

 

 

문제

수행해야 할 작업 N개 (3 ≤ N ≤ 10000)가 있다. 각각의 작업마다 걸리는 시간(1 ≤ 시간 ≤ 100)이 정수로 주어진다.

 

몇몇 작업들 사이에는 선행 관계라는 게 있어서, 어떤 작업을 수행하기 위해 반드시 먼저 완료되어야 할 작업들이 있다. 이 작업들은 번호가 아주 예쁘게 매겨져 있어서, K번 작업에 대해 선행 관계에 있는(즉, K번 작업을 시작하기 전에 반드시 먼저 완료되어야 하는) 작업들의 번호는 모두 1 이상 (K-1) 이하이다. 작업들 중에는, 그것에 대해 선행 관계에 있는 작업이 하나도 없는 작업이 반드시 하나 이상 존재한다. (1번 작업이 항상 그러하다)

 

모든 작업을 완료하기 위해 필요한 최소 시간을 구하여라. 물론, 서로 선행 관계가 없는 작업들은 동시에 수행 가능하다.

 

 

 

입력

 

첫째 줄에 N이 주어진다.

두 번째 줄부터 N+1번째 줄까지 N개의 줄이 주어진다. 2번째 줄은 1번 작업, 3번째 줄은 2번 작업, ..., N+1번째 줄은 N번 작업을 각각 나타낸다. 각 줄의 처음에는, 해당 작업에 걸리는 시간이 먼저 주어지고, 그 다음에 그 작업에 대해 선행 관계에 있는 작업들의 개수(0 ≤ 개수 ≤ 100)가 주어진다. 그리고 선행 관계에 있는 작업들의 번호가 주어진다.

 

 

 

출력

 

첫째 줄에 모든 작업을 완료하기 위한 최소 시간을 출력한다.

 

 


 

 

문제 해결

 

문제에서 K번 작업의 선행 작업들의 번호는 모두 1이상 K-1 이하라는 조건이 주어졌으므로 dynamic programming 기법을 사용할 수 있습니다. 해결 알고리즘은 간단합니다. K번 작업의 선행 작업들 중 가장 마지막까지 수행된 작업의 시간에 K번의 작업시간을 더해줍니다.

 

 

각 작업이 걸리는 시간을 저장하는 배열을 arr_time[],

dp[i]에 i번 작업이 완료되는 데 걸리는 최소 시간,

task[i][]에 i번 작업의 선행 작업

을 저장하겠습니다.

 

 

한 가지 주의사항이 있습니다. 문제를 잘 읽어보면 선행 작업이 없는 작업은 최소 한 개 이상이며 1번이 그러하다고 적혀있습니다. r을 i번의 선행 작업 번호라고 한다면, dp[i] = max(dp[i], dp[r] + arr_time[i]) 처럼 작성하면 선행 작업이 없는 작업일 때는 이 명령을 수행하지 않으므로 엉뚱한 결과가 나올 수 있습니다. 

 

 

다음과 같은 입력이 주어졌다고 가정하겠습니다.

5

5 0

3 1 1

3 1 2

3 1 3

100 0

 

위 상황은 5개의 작업 중 1번부터 4번까지는 (1 - 2 - 3 - 4)로 연쇄적이고, 5번은 독립적입니다.

 

 

dp[i] = max(dp[i], dp[r] + arr_time[i])로 수행한 결과는 잘못된 값인 14가 나옵니다. 이유는 마지막 입력인 5번 작업은 선행 작업이 없으므로 max 문장이 수행되지 않기 때문입니다. 옳은 결과인 100을 출력하려면 선행 작업이 없는 경우를 대비하여, 선행 작업의 시간이 0 또한 가능하게 만들어줍니다.

 

 

 

코드

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

int n, m, ans, arr_time[10001], dp[10001];
vector<int> task[10001];

int main() {
	cin >> n;

	for (int i = 1; i <= n; i++) {
		cin >> arr_time[i] >> m;

		while (m--) {
			int pretask;
			cin >> pretask;
			task[i].push_back(pretask);
		}
	}

	dp[1] = arr_time[1];

	for (int i = 2; i <= n; i++) {
		int max_pretask = 0;

		for (auto r : task[i])
			max_pretask = max(max_pretask, dp[r]);

		dp[i] = max_pretask + arr_time[i];

		ans = max(ans, dp[i]);
	}

	cout << ans;
}

'🎲 BOJ > 🥈' 카테고리의 다른 글

[C++] 백준 2477 : 참외밭  (0) 2023.02.04
[C++] 백준 10826 : 피보나치 수 4  (0) 2022.02.06
[C++] 백준 1181 : 단어 정렬  (0) 2022.02.04
[C++] 백준 9084 : 동전  (0) 2022.02.03

 

 

 

문제

홍대병에 걸린 도현이는 겹치는 것을 매우 싫어한다. 특히 수열에서 같은 원소가 여러 개 들어 있는 수열을 싫어한다. 도현이를 위해 같은 원소가 K개 이하로 들어 있는 최장 연속 부분 수열의 길이를 구하려고 한다.

 100,000 이하의 양의 정수로 이루어진 길이가 N인 수열이 주어진다.  이 수열에서 같은 정수를 K개 이하로 포함한 최장 연속 부분 수열의 길이를 구하는 프로그램을 작성해보자.

 

 

 

입력

첫째 줄에 정수 N(1 ≤ N ≤ 200000)과 K(1 ≤ K ≤ 100)가 주어진다.

둘째 줄에는 a1, a2, ... , an이 주어진다. (1 ≤ ai ≤ 100000)

 

 

 

출력

조건을 만족하는 최장 연속 부분 수열의 길이를 출력한다.

 

 


 

 

문제 해결

 

이중 for문을 이용한 브루트포스 알고리즘은 탐색 범위를 나름대로 최적화 후 제출해봤지만 아쉽게도 실행 시간 초과가 발생했습니다.

따라서 투 포인터를 이용하여 해결 가능합니다.

 

start와 end라는 두 개의 포인터가 입력된 수열 ary를 순차적으로 탐색하게 됩니다.

 

end ≤ N를 만족하면 다음 내용을 반복합니다.

1. 먼저 end가 ary의 첫 원소부터 이동합니다.

2. 만약 end가 탐색하는 값 t의 개수가 K개보다 커지면, start는 t의 개수가 K개보다 작거나 같아질 때까지 이동합니다.

3. end - start +1개와 현재 최대값을 비교하여 최대값을 갱신합니다.

 

 

 

코드

#include <iostream>
using namespace std;

int ans, n, k, ary[200001], cnt[100001];

int main() {
	cin >> n >> k;

	for (int i = 1; i <= n; i++) scanf("%d", ary + i);

	int start = 1, end = 0;

	while (end < n) {
		end++;
		cnt[ary[end]]++;

		if (cnt[ary[end]] > k)
			while (cnt[ary[end]] > k) {
				cnt[ary[start]]--;
				start++;
			}
		
		ans = max(ans, end - start + 1);
	}

	cout << ans;
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 5430 : AC  (0) 2023.08.06
[C++] 백준 4256 : 트리  (0) 2023.08.06
[C++] 백준 1072 : 게임  (0) 2022.02.08
[C++] 백준 2533 : 사회망 서비스(SNS)  (0) 2022.02.07

 

 

 

문제

김형택은 지금 몰래 Spider Solitaire(스파이더 카드놀이)를 하고 있다. 형택이는 이 게임을 이길 때도 있었지만, 질 때도 있었다. 누군가의 시선이 느껴진 형택이는 게임을 중단하고 코딩을 하기 시작했다. 의심을 피했다고 생각한 형택이는 다시 게임을 켰다. 그 때 형택이는 잠시 코딩을 하는 사이에 자신의 게임 실력이 눈에 띄게 향상된 것을 알았다.

 

이제 형택이는 앞으로의 모든 게임에서 지지 않는다. 하지만, 형택이는 게임 기록을 삭제 할 수 없기 때문에, 자신의 못하던 예전 기록이 현재 자신의 엄청난 실력을 증명하지 못한다고 생각했다.

게임 기록은 다음과 같이 생겼다.

 

  • 게임 횟수 : X
  • 이긴 게임 : Y (Z%)
  • Z는 형택이의 승률이고, 소수점은 버린다. 예를 들어, X=53, Y=47이라면, Z=88이다.

 

X와 Y가 주어졌을 때, 형택이가 게임을 최소 몇 번 더 해야 Z가 변하는지 구하는 프로그램을 작성하시오.

 

 

 

입력

각 줄에 정수 X와 Y가 주어진다.

 

 

출력

첫째 줄에 형택이가 게임을 최소 몇 판 더 해야하는지 출력한다. 만약 Z가 절대 변하지 않는다면 -1을 출력한다.

 

 

제한

  • 1 ≤ X ≤ 1,000,000,000
  • 0 ≤ Y ≤ X

 

 


 

 

문제 해결

문제의 난이도 레이팅은 실버3이지만 우매한 저를 2시간이 넘도록 헤매게 만들었기 때문에 난이도 상 카테고리에 써봅니다.

 

 

만약 X가 작았더라면 브루트포스 알고리즘을 사용해서 문제를 풀 수 있지만, X의 입력이 최대 10억까지므로

X = 1,000,000,000 (10억)

Y = 980,000,000 (9억 8천만)

인 경우 구하려는 결과값은 10억이 나오게 됩니다. 따라서 브루트포스 알고리즘은 시간초과로 사용할 수 없습니다.

 

 

대신 이분 탐색 알고리즘을 사용해 볼 수 있습니다.

여기서 제가 상당한 시간을 허비한 이유가 "어떻게 Z 바로 다음으로 승률이 변하는 값을 mid 값으로 찾을 수 있을까?"에 대한 집착(?)이었습니다. 문제 해결을 살펴보겠습니다.

 

 

먼저 승률이 99퍼센트 이상인 경우, 몇 판을 추가적으로 진행하더라도 승률이 오를 수 없습니다. 따라서 이 경우는 예외로 처리해줘야 합니다.

 

 

mid번 만큼 게임을 수행한 결과의 승률을 tmpz라고 할 때, tmpz = 100 * (Y + mid) / (X + mid) 입니다.

 

 

탐색 조건은 두 가지로 나뉘며 탐색의 시작점을 s, 종점을 e, 구하려는 값을 ans라고 두겠습니다.

(1) if (z < tmpz) e = mid - 1

(2) if (z ≥ tmpz) s = mid + 1

 

 

여기서 중요한 점은 s는 tmpz가 z보다 작거나 같으면 mid+1 값으로 바뀐다는 것입니다. 즉, s는 tmpz가 z보다 커지도록 움직입니다. 따라서 탐색 종료 후의 s 값이 처음으로 승률이 바뀌게 되는 값입니다.

 

만약 mid가 정확히 ans 값이더라도, e가 mid-1로 바뀌고 그 후의 반복문은 항상 z ≥ tmpz을 만족시키므로 s는 1씩 증가하다가 while문의 s≤e 조건에 의해 s = e인 경우 한 번 더 수행되어 결국엔 s = mid+1 = ans를 갖게 됩니다.

 

 

 

코드

#include <iostream>
using namespace std;

long long x, y, z, tmpz, s, mid, e;

int main() {
	cin >> x >> y;

	z = y * 100 / x;

	if (z >= 99) { cout << -1; return 0; }

	s = 0;
	e = 1000000000;

	while (s <= e) {
		mid = (s + e) / 2;
        
		tmpz = 100 * (y + mid) / (x + mid);
        
		if (z < tmpz)
			e = mid - 1;
		else
			s = mid + 1;
	}

	cout << s;
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 4256 : 트리  (0) 2023.08.06
[C++] 백준 20922 : 겹치는 건 싫어  (0) 2022.02.09
[C++] 백준 2533 : 사회망 서비스(SNS)  (0) 2022.02.07
[C++] 백준 14226 : 이모티콘  (1) 2022.02.02

 

 

 

문제

페이스북, 트위터, 카카오톡과 같은 사회망 서비스(SNS)가 널리 사용됨에 따라, 사회망을 통하여 사람들이 어떻게 새로운 아이디어를 받아들이게 되는가를 이해하는 문제가 중요해졌다. 사회망에서 사람들의 친구 관계는 그래프로 표현할 수 있는데,  이 그래프에서 사람은 정점으로 표현되고, 두 정점을 잇는 에지는 두 정점으로 표현되는 두 사람이 서로 친구 관계임을 표현한다. 

 

예를 들어, 철수와 영희, 철수와 만수, 영희와 순희가 서로 친구 관계라면 이를 표현하는 친구 관계 그래프는 다음과 같다. 

 

 

친구 관계 그래프를 이용하면 사회망 서비스에서 어떤 새로운 아이디어가 전파되는 과정을 이해하는데 도움을 줄 수 있다. 어떤 새로운 아이디어를 먼저 받아들인 사람을 얼리 아답터(early adaptor)라고 하는데, 사회망 서비스에 속한 사람들은 얼리 아답터이거나 얼리 아답터가 아니다. 얼리 아답터가 아닌 사람들은 자신의 모든 친구들이 얼리 아답터일 때만 이 아이디어를 받아들인다. 

 

어떤 아이디어를 사회망 서비스에서 퍼뜨리고자 할 때, 가능한 한 최소의 수의 얼리 어답터를 확보하여 모든 사람이 이 아이디어를 받아들이게 하는  문제는 매우 중요하다. 

 

일반적인 그래프에서 이 문제를 푸는 것이 매우 어렵다는 것이 알려져 있기 때문에, 친구 관계 그래프가 트리인 경우, 즉 모든 두 정점 사이에 이들을 잇는 경로가 존재하면서 사이클이 존재하지 않는 경우만 고려한다. 

 

예를 들어, 8명의 사람으로 이루어진 다음 친구 관계 트리를 생각해보자. 2, 3, 4번 노드가 표현하는 사람들이 얼리 아답터라면, 얼리 아답터가 아닌 사람들은 자신의 모든 친구가 얼리 아답터이기 때문에 새로운 아이디어를 받아들인다.

 

 

친구 관계 트리가 주어졌을 때, 모든 개인이 새로운 아이디어를 수용하기 위하여 필요한 최소 얼리 어답터의 수를 구하는 프로그램을 작성하시오.

 

 

입력

 

첫 번째 줄에는 친구 관계 트리의 정점 개수 N이 주어진다. 단, 2 ≤ N ≤ 1,000,000이며, 각 정점은 1부터 N까지 일련번호로 표현된다. 두 번째 줄부터 N-1개의 줄에는 각 줄마다 친구 관계 트리의 에지 (u, v)를 나타내는 두 정수 u와 v가 하나의 빈칸을 사이에 두고 주어진다. 

 

 

출력

 

주어진 친구 관계 그래프에서 아이디어를 전파하는데 필요한 얼리 어답터의 최소 수를 하나의 정수로 출력한다.

 

 


 

 

문제 해결

트리에서의 메모이제이션을 활용한 dp 문제다. 전부터 트리에서도 dp 기법을 쓸 수 있을 것 같다고는 막연하게 생각했는데 이 유형을 문제로 만나보는 경험은 처음이다.

 

 

임의의 한 노드가 얼리 아답터 또는 얼리 아답터가 아니며, 이 노드의 상태에 따라 다른 노드들에 영향을 주므로 2차원 배열 dp[1000001][2]를 선언하여 두 상황을 개별적으로 관리한다.

 

 

dp[i][0]은 루트를 i로 하는 서브트리에서, i가 얼리 아답터가 아닌 경우 필요한 최소 얼리 아답터의 수를 의미한다.

dp[i][1]은 루트를 i로 하는 서브트리에서, i가 얼리 아답터인 경우 필요한 최소 얼리 아답터의 수를 의미한다.

 

 

 

문제 풀이의 핵심 개념은 다음과 같다.

✔ 임의의 노드 X가 얼리 아답터일 때 (dp[X][1])      →       주변 노드들은 얼리 아답터이거나 아닐 수 있다.

✔ 노드 X가 얼리 아답터가 아닐 때 (dp[X][0])         →       주변 노드들은 모두 얼리 아답터여야만 한다.

 

 

 

따라서 dp[X][0]은 X의 모든 자식 노드들이 얼리 아답터인 경우를 더해주고,

dp[X][0] += dp[child][1];

 

 

dp[X][1]은 X의 모든 자식 노드들이 얼리 아답터인 경우와 그렇지 않은 경우 중 작은 값을 더해주면 된다. 추가로 이 경우에는 X 자신도 얼리 아답터이기 때문에 최종적으로 1만큼 증가시킨다.

dp[X][1] += min(dp[child][0], dp[child][1]);

// ... (생략) ...

dp[X][1]++;

 

 

 

 

코드

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

int n, u, v, dp[1000001][2];
bool visit[1000001];
vector<int> tree[1000001];

void dfs(int current) {

	visit[current] = 1;

	dp[current][0] = 0;
	dp[current][1] = 1;

	for (auto child : tree[current]) {
		if (visit[child]) continue;

		dfs(child);

		dp[current][0] += dp[child][1];
		dp[current][1] += min(dp[child][0], dp[child][1]);
	}
}

int main() {

	cin >> n;

	for (int i = 1; i < n; i++) {

		scanf("%d %d", &u, &v);

		tree[u].push_back(v);
		tree[v].push_back(u);
	}

	dfs(1);

	cout << min(dp[1][0], dp[1][1]);
}

'🎲 BOJ > 🥇' 카테고리의 다른 글

[C++] 백준 20922 : 겹치는 건 싫어  (0) 2022.02.09
[C++] 백준 1072 : 게임  (0) 2022.02.08
[C++] 백준 14226 : 이모티콘  (1) 2022.02.02
[C++] 백준 2206 : 벽 부수고 이동하기  (0) 2022.01.30

 

 

 

문제

피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다.

이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n ≥ 2)가 된다.

 

n=17일때 까지 피보나치 수를 써보면 다음과 같다.

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597

 

n이 주어졌을 때, n번째 피보나치 수를 구하는 프로그램을 작성하시오.

 

 

입력

첫째 줄에 n이 주어진다. n은 10,000보다 작거나 같은 자연수 또는 0이다.

 

 

출력

첫째 줄에 n번째 피보나치 수를 출력한다.

 

 


 

 

문제 해결

피보나치 수열을 10000번째까지 구하는 일은 어떠한 프로그래밍 언어도 숫자로는 나타낼 수 없습니다. 이유는 그 수가 상상이상으로 무지막지하게 크기 때문인데요.

 

실제로 구해본 결과는 아래 사진과 같았습니다.

10000번째 피보나치 수

 

(진짜 큽니다 ; 처음엔 코드 잘못 작성한 줄)

 

따라서 문자열로 구현해야 합니다. 저는 올림수를 고려하여 맨 뒤를 일의 자리가 아닌, 맨 앞을 일의 자리로 두었습니다. 마지막에 출력할 때만 거꾸로 출력하면 되니까요.

 

carry를 0으로 초기화하고, 각 자리를 더할 때 같이 더해줍니다. 그러고 더한 결과 sum의 일의 자리를 반환 문자열 res에 넣고, carry = sum / 10으로 설정합니다. 그러면 carry는 sum이 10 미만일 때 0, 10 이상일 때는 1로 알아서 바뀌게 됩니다.

 

코드로 살펴보겠습니다.

 

 

 

 

코드

#include <iostream>
using namespace std;

int n;
string fib[10001] = { "0","1" };

string fib_sum(string a, string b) {

	string res;

	int alen = a.size(),
		blen = b.size(),
		pos = 0,
		carry = 0,
		sum;

	while (pos < alen && pos < blen) {
		sum = (a[pos] - '0') + (b[pos] - '0') + carry;
		res += sum % 10 + '0';	// 일의 자리 숫자를 문자로 변환하여 반환 문자열에 추가
		carry = sum / 10;
        	pos++;
	}

	while (pos < alen) {
		sum = (a[pos] - '0') + carry;
		res += sum % 10 + '0';
		carry = sum / 10;
		pos++;
	}

	while (pos < blen) {
		sum = (b[pos] - '0') + carry;
		res += sum % 10 + '0';
		carry = sum / 10;
		pos++;
	}

	if (carry) res += carry + '0';	// 마지막 올림수가 있는 경우 문자열에 추가

	return res;
}

int main() {
	cin >> n;

	for (int i = 2; i <= n; i++)
		fib[i] = fib_sum(fib[i - 1], fib[i - 2]);

	for (int i = fib[n].size() - 1; i >= 0; i--)
		cout << fib[n][i];
}

'🎲 BOJ > 🥈' 카테고리의 다른 글

[C++] 백준 2477 : 참외밭  (0) 2023.02.04
[C++] 백준 2056 : 작업  (0) 2022.02.10
[C++] 백준 1181 : 단어 정렬  (0) 2022.02.04
[C++] 백준 9084 : 동전  (0) 2022.02.03

+ Recent posts