한국정보올림피아드 2016년도 초등부 문제 풀이 포스팅입니다.


문제들은 백준저지에서 참고하고 풀이했습니다.

https://www.acmicpc.net/category/detail/1526


총 4문제로 이루어지며, 뒤쪽 문제로 갈 수록 난이도가 어려워집니다.


1번 방 배정 문제입니다.

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


학년 별 인원수에 맞는 방을 배정한 뒤 모두 더해주면 됩니다.

방에 4명씩 있을 수 있다면, 실제 인원에 +3을 해준 뒤, 4로 나누어주면 각 학년 및 성별별로 필요한 방의 수가 나옵니다. C++에서 정수끼리 나눗셈을 할 시, 소수점 이하는 자동으로 버려지기 때문입니다.


예를들어 방에 5명이 있을 수 있고, 인원이 5명이라고 하면 5+4을 해서 9을 만든 뒤 5로 나누면 1.8이 되는데 소수점 버림으로 인하여 1이 됩니다. 5명일 시에는 방이 1개 필요한 것입니다.

비슷한 원리로 방에 5명이 있을 수 있고, 인원이 6명일 경우 6+4을 해서 10를 만든 뒤 5로 나누면 2가 됩니다.  6명이 묵기 위해서는 2개의 방이 필요한 것입니다.



#include <iostream>

#define GIRL 0
#define BOY 1
using namespace std;

int people[2][7];
int main() {
	int N, K;
	int ans = 0;
	int bias;
	cin >> N >> K;
	
	bias = K - 1;
	
	for (int i=0; i < N; i++) {
		int S, Y;
		cin >> S >> Y;
		people[S][Y]++;
	}
	for (int s = 0; s < 2; s++) {
		for (int y = 1; y < 7; y++) {
			if (people[s][y]) {
				ans += (people[s][y] + bias) / K;
			}
		}
	}
	cout << ans << endl;
	
}


2번 타일 장식물 문제입니다.

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

가로 세로 길이가 늘어나는 패턴이 짝수번째 홀수번째마다 규칙성을 갖고 연속됩니다. 이 규칙성만 찾으면 간단하게 해결할 수 있습니다.


#include <iostream>

using namespace std;
typedef long long ll;
int main() {
	ll x = 1, y = 1;
	int N;
	cin >> N;
	for (int i = 1; i < N; i++) {
		if (i & 1) {
			y += x;
		} else {
			x += y;
		}
	}
	
	cout << ((x+y)<<1) << endl;
}
a

3번 리조트 문제입니다.



 경우의 수가 많지 않으니, DFS로 완전탐색을 하면 쉽게 찾을 수 있습니다. 쿠폰을 항상 쓰는 것이 능사가 아닌 케이스가 있다는 것을 조심해야합니다. 그런데 그냥 DFS를 하면 중복 케이스를 찾는 오버해드때문에 타임아웃이 날 수 있으므로, Memoization을 활용한 동적 프로그래밍의 테크닉도 섞어주어야 합니다.

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>

#define MIN(a,b) ((a) < (b) ? (a) : (b))
using namespace std;

vector<int> holiday;
vector<bool> day;
int total_day, absent_day;
int cost[101][101];

int count = 0;
int count_mem = 0;
int find_cost(int start_day, int num_coupon) {
	count ++;
	int min_cost = 987654321;
	int temp, a,b;
	temp = 987654321;
	if (start_day > total_day) {
		//basis case
		return 0;
	} else if (!day[start_day]) {
		
		for (int i=start_day+1; i<=total_day; i++) {
			if (day[i]) {
				return find_cost(i, num_coupon);
			}
		}
		return 0;
	}
	
	if (cost[start_day][num_coupon] == 0) {
		if ( start_day <= total_day - 2 && day[start_day+1] && day[start_day+2] ) {
			
		} else {
			min_cost = MIN(min_cost, find_cost(start_day + 1, num_coupon) + 10000 );
		}
		
		if (min_cost != temp) {
			temp = min_cost;
			a = start_day + 1;
			b = num_coupon;
		}
		
		if ( start_day <= total_day - 5 && day[start_day+1] && day[start_day+2] && day[start_day+3] && day[start_day+4]) {
		} else {
			min_cost = MIN(min_cost, find_cost(start_day + 3, num_coupon + 1) + 25000 );
		}
		
		if (min_cost != temp) {
			temp = min_cost;
			a = start_day + 3;
			b = num_coupon + 1;
		}
		
		min_cost = MIN(min_cost, find_cost(start_day + 5, num_coupon + 2) + 37000 );
		
		if (min_cost != temp) {
			temp = min_cost;
			a = start_day + 5;
			b = num_coupon + 2;
		}
		
		if (num_coupon >= 3) {
			min_cost = MIN(min_cost, find_cost(start_day + 1, num_coupon - 3) );
			
			if (min_cost != temp) {
				a = start_day + 1;
				b = num_coupon - 3;
			}

		}
		cost[start_day][num_coupon] = min_cost;
		return min_cost;
	} else {
		count_mem++;
		return cost[start_day][num_coupon];
	}
	
	
}
int main() {
	int temp;
	int answer;
	
	scanf("%d %d\n", &total_day, &absent_day);
	holiday.reserve(absent_day);
	day.resize(total_day + 1, true);

	for (int i=0;i<absent_day; i++) {
		scanf("%d ", &temp);
		holiday.push_back(temp);
	}
	
	for (int i=0;i<absent_day; i++) {
		day[holiday[i]] = false;
	}
	
	answer = find_cost(1, 0);
	cout << answer;
}


4번 장애물 경기 문제입니다.

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

솔루션 해결에 많은 고민을 했습니다. 이게 초등부 문제가 맞는지 의심이 갈 정도였습니다. ㅠㅠ

이 문제는 해답을 보고 풀기도 했고, 100% 이해를 하고 난 문제가 아니라서 지금은 정확한 풀이가 잘 기억은 나지 않습니다만, 아래 코드는 정답 인정이 되는 코드입니다.

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

typedef struct {
	int x, yl, yh;
} barPos;

typedef pair<int, int> meta;
bool operator<(barPos a, barPos b) {
	return a.x < b.x;
}

vector<barPos> barriers;
vector<int> answer;
set<meta> subp;
void dump_barrier() {
	cout << " --------------------------------------" << endl;
	for (int i=0; i < barriers.size(); i++) {
		cout << barriers[i].x << " " << barriers[i].yl << " " << barriers[i].yh << endl;
	}
	cout << " --------------------------------------" << endl;
}
void dump_subp() {
	for (set<meta>::iterator ptr = subp.begin(); ptr != subp.end(); ptr++) {
		cout << ptr->first << ", " << ptr->second << endl;
	}
}
int find_min_y() {
	int min_val = 98754321;
	for (set<meta>::iterator ptr = subp.begin(); ptr != subp.end(); ptr++) {
		min_val = min(min_val, ptr->second);
	}
	return min_val;
}
void set_answers(int min_val) {
	for (set<meta>::iterator ptr = subp.begin(); ptr != subp.end(); ptr++) {
		if (ptr->second == min_val) {
			answer.push_back(ptr->first);
		}
	}
}
int main() {
	int N;
	int srcY, finX;
	
	cin >> N >> srcY >> finX;
	barriers.reserve(N);
	
	for (int i=0; i < N; i++) {
		int x, yl, yh;
		
		cin >> x >> yl >> yh;
		barPos tmp;
		tmp.x = x;
		tmp.yl = yl;
		tmp.yh = yh;
		barriers.push_back(tmp);
	}
	sort(barriers.begin(), barriers.end());
	subp.insert(make_pair(srcY, 0));
	vector<set<meta>::iterator> delete_candidate;
	for (int i=0; i < N; i++) {
		auto upper_bound = barriers[i].yh;
		auto lower_bound = barriers[i].yl;
		delete_candidate.clear();
		int minl = 987654321;
		int minh = 987654321;
		for (set<meta>::iterator ptr = subp.lower_bound(make_pair(lower_bound, 0)); ptr != subp.upper_bound(make_pair(upper_bound, 0)); ptr++) {
			delete_candidate.push_back(ptr);
			minl = min(minl, ptr->second + (ptr->first - lower_bound));
			minh = min(minh, ptr->second + (upper_bound - ptr->first));
		}
		for (int j=0; j < delete_candidate.size(); j++) {
			subp.erase(delete_candidate[j]);
		}
		if (delete_candidate.size() > 0) {
			delete_candidate.clear();
			subp.insert(make_pair(upper_bound, minh));
			subp.insert(make_pair(lower_bound, minl));
		}
	}
	
	int min_y_val = find_min_y();
	set_answers(min_y_val);
	
	cout << finX + min_y_val << endl << answer.size();
	for (int i=0; i < answer.size(); i++) {
		cout << ' ' << answer[i];
	}
	cout << endl;
}




이클립스는 자바 오픈소스 IDE로 유명합니다. 하지만 자바뿐만 아니라 C/C++ 개발도구의 기능도 갖는데요, 이를 이클립스 CDT라고 합니다. 이번 포스팅에서는 윈도우즈 환경에 이클립스 CDT를 설치하는 내용을 다루어보고자 합니다.

 

일단 이클립스 CDT를 설치하는 방법은 크게 두가지가 있습니다.

 

1. 일반 이클립스를 설치 후, CDT 플러그인을 추가적으로 설치한다.

2. 이클립스 CDT통합본을 설치한다.

 

위 처럼 두가지 방법 다 제공하는 것은 이클립스로 스프링 프레임워크(Spring framework) 개발환경 구축하는 것과 비슷합니다. 마치 자바 스프링 프레임워크 개발환경인 STS(Spring Tool Suite)를 기존 이클립스에 붙여버린 IDE를 다운받는 것과(링크: https://spring.io/tools/sts/all) 기존 이클립스를 설치한 후, Marketplace에서 STS 플러그인을 내려받아서 설치하는 방법의 두가지가 존재하는 것 처럼요.

 

 

이클립스 CDT 홈페이지를 한번 방문 해 봅니다.

https://eclipse.org/cdt/

 

위처럼 공식 홈페이지에 들어가면 이클립스 CDT 프로젝트에 대한 대강 설명이 나옵니다. 노란색의 다운로드 버튼을 클릭해보면 다음과 같은 페이지가 나옵니다.

 

이클립스 버전들에 대하여 CDT가 보입니다. 지금 포스팅하는 시기에 최신인 Eclipse Oxygen 버전기준으로 설치 해보도록 하겠습니다. 노란색 형광표시가 된 링크를 클릭합니다.

 

그러면 위 페이지가 나타나게 되는데, 저 같은 경우는 윈도우 10 64비트 운영체제를 사용하고 있으므로, Windows 64bit를 클릭해서 다운받도록 하겠습니다.

 

C/C++ 개발환경이고, Git과 연동이 가능하며, Mylyn이라는 테스크 관리 서브시스템을 지원하고 원격 시스템 explorer라는 기능도 된다고 하네요.

 

 

다운로드 링크가 나오네요. 파일명에 eclipse-cpp-oxygen-R-win32-x86_64.zip이라고 나오는데, 중간에 cpp라는 단어가 있는것으로 봐서 기본 이클립스가 아닌 CDT 버전 이클립스를 다운받는게 맞는 것을 확인할 수 있습니다.

 

다운로드를 받으면 .zip이라는 확장자에 맞게 압축파일이 하나 다운되게 되는데 이클립스는 이 zip파일에 대하여 압축만 해제하면 별도의 설치 과정 없이 바로 실행할 수 있습니다.

 

 

압축을 푼 리스트입니다. 이클립스 모양 아이콘에 이름이 eclipse인 파일을 더블클릭해서 실행 해줍니다.

 

이클립스를 실행하면, 기본 워크스페이스(workspace) 즉, 이클립스 IDE에서 만든 프로젝트들이 기본적으로 저장되는 폴더를 어떻게 설정할 것인지를 물어보는데, 저는 그냥 기본값으로 하고 Launch를 누르겠습니다.

 

이클립스가 실행되면 하단과 같은 창이 나타날 것입니다.

 

Welcome 페이지라고 이클립스의 강력한 기능들을 소개해주는 그러한 페이지가 나타나게 되는데, 왼쪽 상단의 빨간색으로 마킹된 버튼을 누르면 프로젝트와 관련된 기능창들이 확장되게 됩니다. 그리고 Welcome 페이지가 다음 시작때는 나타나지 않게 하려면 우측 하단의 체크박스를 해제해주시면 됩니다.

 

IDE 개발창으로 돌아가게 되면 하단에 다음과 같은 에러가 아마 나타나게 됩니다.

에러가 8개 정도 나타났는데, 이 나머지 6개는 제가 예제로 올린 소스코드때문에 나타난 에러로 무시하셔도 됩니다. 하지만 중요한 2개의 에러는 다음과 같은 구문인데요.

 

Program "g++" not found in PATH

Program "gcc" not found in PATH

 

이 에러 구문을 해결해보도록 하겠습니다.

 

이클립스 IDE가 제공하는 기능은 소스코드 에디팅, 린팅(Linting), 빌드(컴파일+링킹), 실행과 같은 IDE가 제공하는 기능들인데요, 사실 이클립스는 이러한 기능을 사용자가 GUI로 쉽게 할 수 있도록 껍데기를 제공하는 것이지 컴파일러나 링커등을 직접 내장하고 있지 않습니다. 이클립스로 자바 개발을 할 때 JDK(Java Development Kit)을 직접 설치하고 환경변수를 설정해야 하는 것 처럼, 이클립스 CDT 역시 C/C++ 개발 SDK를 설정해야 합니다. 이 때 g++는 C++ 컴파일러 셋을 뜻하고, gcc는 C 컴파일러 셋을 뜻합니다.

 

gcc와 g++는 원래 유닉스/리눅스 환경에서 사용되는 C/C++ 컴파일러인데 윈도우 환경에서는 해당 컴파일러들의 포팅된 버전이 존재합니다. MinGW와 Cygwin이 있습니다.

 

즉, 해당 C/C++ 컴파일러를 설정하는데에도 2가지 옵션이 있습니다.

 

1. MinGW gcc g++ 설치

2. Cygwin gcc g++ 설치

 

본 포스팅에서는 Cygwin을 이용하여 설정해보도록 하겠습니다.

 

일단 Cygwin 홈페이지를 방문합니다.

https://www.cygwin.com/

Cygwin 프로젝트에 대한 설명이 어느정도 나와 있습니다. 윈도우에서 리눅스처럼 사용할 수 있게한다는 내용입니다. 좌측 상단에 Install Cygwin을 누릅니다.

윈도우즈 운영체제 bit수에 따라 골라서 설치할 수 있습니다. 저는 64bit버전인 setup-x86_64.exe를 더블클릭해서 다운로드 받겠습니다. 다운로드 받은 뒤 더블클릭 해서 설치를 합니다.

그냥 다음을 눌러줍니다.

다운로드 소스를 고르는 곳입니다. 리눅스의 경우 필요한 패키지들을 apt나 yum, dnf 등과 같은 패키지 매니저를 커맨드라인에 치는 것만으로도 쉽게 설치가 가능하지만, cygwin에서는 필요한 패키지를 저 인스톨 하는 과정에 설정을 해서 설치를 해야 합니다. 만약 우분투였으면 apt-get install -y g++ 과 같은 커맨드로 되는 내용을 cygwin에서는 저 설치파일을 이용해서 설치해야 합니다. 이러한 필요한 것들을 인터넷으로 받겠다는 내용입니다.

 

cygwin이 설치될 경로를 지정하는 부분입니다. 기본값으로 한 뒤 계속 다음을 눌러줍니다.

필요한 패키지들 다운받는 url을 고르는 페이지입니다. 가급적 한국에 가깝고 빠른 저장소 url을 안다면 패키지를 좀 더 빨리 받을 수 있겠지만 아무곳이나 해도 무방합니다.

이 화면에서 gcc와 g++을 골라서 설치하도록 합니다. 좌측 상단의 View를 Full로 해서 전체가 다 보이게 한 뒤, Search 필드에 gcc라고 입력합니다. 그러면 다음과 같은 리스트들이 나타나게 되는데, Package에 gcc-core: GNU Compiler Collection(C, OpenMP) 저녀석과, gcc-g++: GNU Compiler Collection(C++)를 선택합니다. 지금 저는 이미 설치가 된 상태라서 New 탭에 Keep이라고 나오는데, 아마 Skip라고 나와 있을 것입니다. 해당 Skip이라는 부분을 눌러서 숫자(버전)이 나오도록 바꾸어줍니다. Bin? 항목에 n/a 대신 체크박스에 X표시가 나타나도록 되면 잘 된 것입니다.

 

 

이후 다음 버튼을 눌러서 설치를 끝냅니다.

정상적으로 설치되었다면 다음 경로에 gcc.exe g++.exe라는 파일이 생성되었을 것입니다.

 

C:\cygwin64\bin

 

위 경로는 제가 설치한 경로 기준의 경로입니다. 파일을 확인해봅니다.

이후 환경변수 설정을 해 줍니다. PATH라는 환경변수에 위에 나타난 경로를 지정해서, 어느 곳에서나 gcc나 g++라는 명령어를 쳤을 때 해당 경로에 있는 gcc.exe와 g++.exe가 실행되도록 설정하는 부분입니다.

위에 빨간 마크된 곳들을 순서대로 클릭해서 다음과 같이 등록되도록 합니다.

새로만들기를 눌러서 맨 하단에 C:\cygwin64\bin 과 같이 등록되도록 한 뒤 확인 버튼을 누릅니다.

 

잘 되었는지 확인하려면 cmd 창을 하나 띄워서 gcc나 g++라고 그냥 입력했을 때 다음과 같이 나타나면 잘 된 것입니다.

만약 저렇게 나오지 않고, 그런 명령이 없다는 식으로 나오면 환경변수 설정이나 cygwin에서 g++ gcc설치가 제대로 되지 않은 것입니다.

 

여기까지 이클립스 CDT 설치 이후 cygwin c컴파일러 환경변수 설정까지 모두 마쳤습니다.

지난 포스팅인 Lord of SQL Injection 사이트 소개에 이어서 해당 워게임 사이트의 첫번째 문제를 풀어보도록 하겠습니다. 첫번째 몬스터인 그램린을 클릭하자 다음과 같은 페이지가 나타나게 됩니다.


페이지에 나타난 부분은 php 소스코드입니다. 실제 웹사이트에서 php와 같은 서버사이드 언어의 소스코드를 획득할 수 있을 가능성은 매우 낮지만, 공부를 목적으로하는 워게임 사이트이다 보니 소스코드를 보여주고, SQL Injection을 이용하여 공격을 해 보라는 뜻입니다. solve("gremlin")이라는 함수를 실행시키도록 하면 문제가 풀릴 것으로 보입니다.


또 관찰해 보면 mysql_fetch_array라는 함수를 실행시키는 모습이 보입니다. 이것으로 보아서 서버는 MySQL DBMS를 사용한다는 것을 알 수 있습니다.


$query 부분에 SQL 쿼리를 생성시키는 모습입니다. 인자값으로 $_GET[id]와, $_GET[pw]의 값을 받습니다. 즉 $_GET[pw]는 사용자가 임의로 변경시킬 수 있는 입력값입니다. $query가 실행되었을 때, 만족되는 값이 하나라도 나타나면 성공입니다.


SQL Injection에 처음 입문 시 많이 사용하는 쿼리인 ' or 1=1 # 또는 ' or 1=1 -- 를 사용해 보겠습니다.


url에 맨 마지막 부분에 다음과 같이 추가적으로 입력한 뒤 엔터를 쳐 봅니다.


?id=%27%20or%201=1--%20a

HTTP GET 방식으로 쿼리 파라메터 id에 %27%20or%201=1--%20a라는 값을 넣어서 전송하겠다는 뜻입니다.


여기서 %27은 URL 인코딩이 된 값으로 ' 문자(싱글쿼터)를 뜻합니다.

F12를 눌러서 크롬 개발자도구를 띄운 뒤, Console 탭에 다음과 같이 입력해서 %27가 싱글쿼터가 맞음을 확인할 수 있습니다. 비슷한 원리로 encodeURI('\'')라고 입력하면 %27가 출력되게 됩니다.


쿼리가 위와 같이 변경되면서 클리어가 됩니다.


왜 이러한 SQL 구문을 통해서 클리어가 될 까요? Subline text에 해당 쿼리를 한번 그대로 입력해보겠습니다.

구문 강조(Syntax Highlighting)을 SQL로 설정한 뒤 해당 쿼리를 입력해 보니, -- 문자 이후는 회색처리가 되어있습니다. 즉 주석으로 인식한다는 뜻입니다.


MySQL에서 지원하는 SQL 문법에 대하여 정확하게 알아보려면 해당 공식 사이트의 레퍼런스 메뉴얼을 참조하는 것입니다.

https://dev.mysql.com/doc/refman/5.7/en/comments.html

위 링크는 MySQL에서 주석문에 대한 매뉴얼 페이지입니다.


MySQL에서 주석은 /* */으로 여러 줄 주석처리가 가능하며, #이나 -- 으로 한 줄 주석처리가 가능합니다. 이 때, -- 로 한 줄 주석처리를 할 경우 -- 이후 공백 문자 1칸이 반드시 필요합니다. 이렇게 뒷 부분이 주석처리가 되다 보니 where 구문은 id='' or 1=1으로 인식되는데, 1=1 구문때문에 항상 참 값을 갖게 되어서, 해당 테이블에 있는 모든 id값이 출력되게 됩니다.

+ Recent posts