저저번주쯤 주말에 올해 2020년의 첫 킥스타트 라운드 A가 있었는데, 아무것도 모르고 주말에 잠을 퍼질러 자다가 참여를 못하게 되었다.

 

A라운드는 근데 문제 난이도가 낮아서 빨리풀기 대회였다니 뭐 이런이야기들을 들었는데, 대회 당시에는 참여를 못했지만 나중에 업솔빙이라도 해볼 겸 시간 날때 풀어보았다.

 

이번해의 킥스타트는 저번과는 다르게 Large dataset도 제출한 뒤 바로 full-feedback을 준다고 한다. 맞았는지 틀렸는지를 바로 알려준다는 것은 뭔가 좋은 것 같으면서도 안 좋은것 같기도 하다.

 

바로 한번 문제 풀어보도록 하겠다.

 

문제들은 여기에서 확인하고 다시 풀어볼 수 있다.

 

총 4문제가 있다. 정답률을 보니 난이도가 왼쪽 < 오른쪽 문제로 가는 것 같다.

1. Allocation

문제는 간단하다. N개의 집이 있고, 각각의 집의 가격은 Ai원이다. 그리고 총 B달러가 있을때 가장 많은 집을 사고 싶다고 한다. 몇개를 살 수 있는가?

 

그냥 Ai를 오름차순으로 소팅한 뒤 예산을 넘지 않는 만큼만 진행하면 된다. //(O(NlgN)//)로 풀리며, N이 //(10^5//)이므로 쉽게 풀린다.

 

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

typedef long long ll;
int A[100005];
void solve() {
	int N, B;
	cin >> N >> B;
	for (int i = 0; i < N; i++) {
		cin >> A[i];
	}
	sort(A, A + N);
	int ans = 0;
	for (int i = 0; i < N; i++) {
		if (B >= A[i]) {
			B -= A[i];
			ans++;
		}
		else {
			break;
		}
	}
	printf("%d\n", ans);
}
int main() {
	int tc; cin >> tc;
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: ", t);
		solve();
	}
}

 

2. Plates

접시를 골라서 그 값들을 최대로 해야하는데, 접시는 위에서부터 연속된 것들만 고를 수 있다. 접시를 고르는 총 개수도 정해져있다.

 

dp문제인데, 맨처음에 dp인 것 같다가 시간복잡도가 안될 것 같았다는 생각이 좀 들어서 약간 긴가민가 했었다. 근데 문제를 부분문제로 나눌 수 있고 독립되게 풀린다는 것이 보여서  dp가 맞는 듯 했다.

 

2차원 dp로 풀 수 있는데 각 항의 정의는 다음과 같다.

//(dp[i][j]//)는 i번째 줄까지 쭉 진행했을 때, j개의 접시를 골라서 낼 수 있는 최대 점수

 

이러면 //(dp[i][j]//)를 이용해서 다음 줄에서 k개의 접시를 구하는 경우//(dp[i+1][j+k]//)를 쉽게 구할 수 있다.

for-loop를 돌리는 식으로 구현해보았다.

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

typedef long long ll;
int a[55][33];
int dp[55][1505]; //dp[i][j]는 i번째 라인까지 j개의 plate를 골라서 얻을 수 있는 최대 점수.
void solve() {
	int N, K, P;
	cin >> N >> K >> P;
	memset(dp, 0, sizeof(dp));
	memset(a, 0, sizeof(a));
	for (int i = 1; i <= N; i++) {
		for (int j = 1; j <= K; j++) {
			cin >> a[i][j];
			a[i][j] += a[i][j - 1];
		}
	}

	
	for (int i = 1; i <= K; i++) {
		dp[1][i] = a[1][i];
	}

	for (int i = 1; i <= N; i++) {
		for (int j = 0; j <= i * K; j++) {
			for (int k = 0; k <= K; k++) {
				dp[i + 1][j + k] = max(dp[i + 1][j + k], dp[i][j] + a[i + 1][k]);
			}
		}
	}
	printf("%d\n", dp[N][P]);
	return;
}
int main() {
	int tc; cin >> tc;
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: ", t);
		solve();
	}
}

 

3. Workout

이거는 증가하는 수열에서 인접한 수열 사이의 차이를 일정 값 이하로 만들고 싶은 상황이다. 이 때  수열 사이 사이에 임의의 값을 갖는 녀석을 끼워넣을 수 있다. 이 때 인접한 수열 사이의 차이값의 최소값을 구하는 그런 문제이다.

 

최소값 구하는 문제, 최대값 구하는 문제 중 이러한 류의 문제들, 차이값의 최소 와 같은 좀 복잡한 값의 최소/최대문제는 보통 결정문제로 변경해서 푸는 파라메트릭 서치 문제이다.

 

인접한 값의 차이를 d 이하로 만들 수 있는가? 의 결정 문제로 바꾼 뒤 이녀석을 통해서 binary search를 돌리면 풀린다.

그리고 이 결정 문제는 보통 그리디나 선형 시간복잡도로 풀리게 된다.

 

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

int M[100005];
int N, K;
bool ok(int v) {
	int k = K;
	for (int i = 1; i < N; i++) {
		int diff = M[i] - M[i - 1];
		k -= (diff + v - 1) / v - 1;
	}
	return k >= 0;
}
int solve() {
	cin >> N >> K;
	for (int i = 0; i < N; i++) {
		cin >> M[i];
	}
	int l = 1;
	int r = 1e9;
	while (l < r) {
		int m = (l + r) / 2;
		if (ok(m)) {
			r = m;
		}
		else {
			l = m + 1;
		}
	}
	return r;
}
int main() {
	int tc;
	cin >> tc;
	for (int t = 1; t <= tc; t++) {
		cout << "Case #" << t << ": " << solve() << endl;
	}
}

 

4. Bundling

문자열들을 같은 크기의 Group들로 균일하게 나눈 뒤, 그 그룹들의 Longest prefix의 길이만큼 점수를 얻는데 이 점수를 최대화하는 그런 문제이다. 그룹을 어떻게 나눌 것인가까지는 알 필요 없이 점수만을 계산할 수 있다고 한다. 맨처음 이 문제를 보고 문자열 그냥 소팅한 뒤 K개씩 그룹핑 하면 되지 않을까 했는데 조금 생각해보니 바로 반례가 나왔다..

 

K가 만약 3이라고 하면 문자열이 다음과 같이 나왔을때 그리디하게 하면 최적화되지 않은 그룹핑이 된다.

A A A B C C C D E E E F

 

이런 경우 하나씩만 있는 B, D, F는 어차피 점수를 낼 수 없으므로 자기내들끼리 묶여서 남에게 피해를 주지 않아야 하는데 greedy하게 묶으면 망한다.

 

솔루션을 보니, Trie를 구축한 뒤, Trie Node별로 나타난 횟수를 체크해놓고, 나타난 횟수를 A라고 할때 각 노드별 floor(A/K)를 더하면 된다고 한다.

 

A % K에 해당하는 K개가 되지 못한 그룹녀석들은 어차피 어디에 끼여도 점수를 낼 수 없으므로 버리게 된다.

조금 생각해보니 correct한 알고리즘인 것이 이해가 되었다.

 

Trie는 malloc으로 구현하는 것이 귀찮아서 배열을 두고 정적할당과 같은 테크닉으로 구현을 해 보았다.

 

그리고 dfs로 모든 노드를 순회하면서 점수를 계산했다.

 

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

#define MAXN 2000006
class Node {
public:
	char c;
	int num;
	int next[28];
	Node() {
		clear();
	}
	void clear() {
		memset(next, 0, sizeof(next));
		num = 0;
		c = 0;
	}
};
Node nodes[MAXN];
int cnt;
int dfs(int v, int k) {
	auto& cnode = nodes[v];
	int ret = cnode.num / k;
	for (char c = 'A'; c <= 'Z'; c++) {
		if (cnode.next[c - 'A'])
			ret += dfs(cnode.next[c - 'A'], k);
	}
	return ret;
}
int solve() {
	int n, k;
	cin >> n >> k;
	//clear prev data
	for (int i = 0; i <= cnt; i++) {
		nodes[i].clear();
	}
	cnt = 0;
	for (int i = 0; i < n; i++) {
		string s;
		cin >> s;
		int ptr = 0;
		for (char c : s) {
			auto& next = nodes[ptr].next[c - 'A'];
			if (!next) {
				next = ++cnt;
				nodes[next].c = c;
			}
			ptr = next;
			nodes[ptr].num++;
		}
	}
	
	return dfs(0, k);
}
int main() {
	int tc;
	cin >> tc;
	for (int t = 1; t <= tc; t++) {
		cout << "Case #" << t << ": " << solve() << endl;
	}
}

알고리즘 문제를 풀다 보면 그래프를 그려봐야 하는 경우 

 

손으로 직접 그리면 번거롭고 노드 갯수가 많은 경우 그리기가 힘들 수 있는데, 이를 간단하게 해결해주는 사이트가 있다.

 

 

 

https://csacademy.com/app/graph_editor/

 

CS Academy

 

csacademy.com

 

csacademy라는 사이트는 코드포스와 탑코더처럼 온라인 알고리즘 콘테스트를 열어주는 그런 사이트인데, 거기 툴중에 그래프 에디터가 있습니다.

 

사용방법도 직관적이고, 유용합니다.

 

왼쪽에 있는 창에 Graph Data를 쓰면 됩니다. Node Count이런것들은 자동으로 생성되므로, 크게 신경쓰지 않아도 되고

 

한 줄에 하나의 숫자만 쓰면, Node가 있음을 선언하는 것이고, 한 줄에 두개의 숫자를 쓰면 왼쪽 노드 번호에서 오른쪽 번호 노드로 가는 Edge가 있는 것을 알려주는 것입니다.

 

무향/방향 그래프임을 위에 Undirected / Directed 버튼으로 결정할 수 있습니다.

 

 

오른쪽 트레이에 보면 상단에 5개 정도 패널이 있는데 각각 모드를 설정할 수 있습니다.

 

Force모드면 노드를 드래그 앤 드롭하면 탄력적으로 움직이고, Draw모드를 하면 이동하고 위치가 고정됩니다.

 

Edit모드는 이미지 상에서 노드 이름을 변경하거나 Edge의 Weight값을 결정하는 등 여러가지가 가능합니다.

 

특히나 Config 패널로 가서 Run Command -> Arrange as Tree로 하면 트리모양으로 그래프를 배열해줍니다.

 

싸이클이 있는 그래프이므로 완전한 Tree모양으로 배열해주진 않지만, 얼추 트리에 가까운 모양으로 배열해줍니다.

 

백문이 불여일견인 툴이므로, 접속해서 애용해봅시다.

bits/stdc++.h 헤더 파일이란?

#include <bits/stdc++.h>

간혹 다른사람들이 C++을 이용하여 알고리즘 문제를 푸는 소스코드를 참조했을 때, 위와 같은 헤더가 나타나는 경우가 있습니다. 이 헤더는 어떤 것이고 어떠한 이유로 사용하는 것일까요?

 

bits/stdc++.h 헤더는 모든 표준 라이브러리가 포함된 헤더입니다. 프로그래밍 대회에서 위 해더를 사용하는 것은 좋은 아이디어 입니다. 문제를 풀 때 마다 #include <iostream> 등등을 작성하는 반복적인 일을 줄여서 시간안배를 도와줍니다. 특히나 문제 푸는 시간이 순위에 결정적인 경우에 더 도움이 될 수 있습니다. 프로그래밍 대회에서는 효율적이고 정확한 알고리즘을 찾는 것이 주가 되어야 하기 때문입니다.

 

하지만 소프트웨어 공학적인 측면에서는 #include 구문을 줄이는 것이 중요합니다. 많은 필요없는 파일들을 #include 하게 된다면, 컴파일 시간과 프로그램 크기가 쓸데없이 길어지고 커지게 됩니다.

 

다음은 bits/stdc++ 헤더의 단점들입니다.

- bits/stdc++.h 헤더는 GNU C++ 라이브러리의 표준 헤더가 아니기 때문에, GCC가 아닌 다른 컴파일러로 빌드를 하려고 한다면 실패합니다.

- 쓸대없는 파일들을 추가시켜서 컴파일 시간이 늘어납니다.

- 표준 C++이 아니기 때문에 이식성이 있지도 않고, 컴파일러 종속적입니다.

 

 

다음은 bits/stdc++ 헤더의 장점들입니다.

- 프로그래밍 대회에서 쓸데없는 시간낭비를 줄여주므로 사용하면 좋습니다.

- 필요한 헤더 파일 include 구문을 작성하는데 시간을 줄여줍니다.

- STL이나 GNU C++의 모든 함수들을 기억할 필요가 없습니다.

 

리눅스 머신에서 해당 헤더파일을 한번 열어봤습니다.

 

// C++ includes used for precompiling -*- C++ -*-

// Copyright (C) 2003-2015 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.

// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// Under Section 7 of GPL version 3, you are granted additional
// permissions described in the GCC Runtime Library Exception, version
// 3.1, as published by the Free Software Foundation.

// You should have received a copy of the GNU General Public License and
// a copy of the GCC Runtime Library Exception along with this program;
// see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
// <http://www.gnu.org/licenses/>.

/** @file stdc++.h
 *  This is an implementation file for a precompiled header.
 */

// 17.4.1.2 Headers

// C
#ifndef _GLIBCXX_NO_ASSERT
#include <cassert>
#endif
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>

#if __cplusplus >= 201103L
#include <ccomplex>
#include <cfenv>
#include <cinttypes>
#include <cstdalign>
#include <cstdbool>
#include <cstdint>
#include <ctgmath>
#include <cwchar>
#include <cwctype>
#endif

// C++
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>

#if __cplusplus >= 201103L
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#endif
 

헤더파일 길이는 100줄 남짓으로, 헤더치고는 별로 길지 않고, 거의다 쓸만한 헤더파일을 다 넣어버린 내용입니다.

 

 

 

윈도우즈 Visual Studio에서 bits/stdc++.h 헤더 사용하기

그런데 만약 이 헤더파일을 Windows Visual Studio에서 사용을 하고 싶다면 어떻게 하는 게 좋을까요?

 

일단 이 bits/stdc++.h 헤더파일은 비표준이기때문에 개발 등에서 사용하기엔 좋은 방법은 아닙니다.

 

하지만 알고리즘 문제해결 등을 할 것이라면, 일반적인 백준이나 코드포스와 같은 온라인 저지에서는 bits/stdc++.h 헤더를 다 지원하기 때문에, 윈도우에서 알고리즘 문제 풀이용으로 헤더를 사용해 보는 것은 나쁘지 않을 수 있습니다.

 

그러면 간단하게, 윈도우에서 적용되도록 비쥬얼 스튜디오 include 헤더들이 저장되는 디렉토리에 bits라는 폴더를 하나 만들고, 그 폴더 하위에 stdc++.h라는 이름의 파일을 집어넣으면 됩니다.

 

일단 윈도우즈 비쥬얼 스튜디오의 include 파일들이 저장되는 디렉토리를 찾아봅시다.

 

제 컴퓨터의 경우는 다음 경로가 include 디렉토리입니다.

 

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\include\

 

물론 Visual Studio 버전과, 설치된 드라이브 명에 따라 조금씩 경로는 다를 수 있습니다.

 

이 경로에 bits라는 디렉토리를 하나 만들어 준 뒤, 그 하위 경로에 stdc++.h라는 텍스트 파일을 하나 만들어 줍시다.

 

(아마 해당 경로에는 바로 텍스트 파일을 만들기에는 관리자 권한이 필요할 수 있으므로, 바탕화면 등에 만든 뒤 드래그 앤 드롭으로 옮기는 식으로 하시는 편이 편합니다.)

 

즉 결과적으로 경로는

 

C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.12.25827\include\bits\stdc++.h

 

가 되게 됩니다.

 

stdc++.h파일의 내용은 아래에 있습니다.

 

 

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

 

 

 

 

+ Recent posts