한국정보올림피아드 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값이 출력되게 됩니다.


웹해킹 공부를 하기 좋은 방법이 여러가지가 있는데, 그 중 하나는 웹해킹 워게임을 푸는 것이라고 생각합니다. 워게임은 해킹관련 문제들을 풀어볼 수 있는 사이트라고 보시면 이해하기 편합니다.


그러한 웹해킹 워게임 사이트 중에서도, SQL Injection이라는 해킹 기법에만 초점을 맞춘 워게임 사이트가 있는데, 통칭 Lord of SQL Injection이라고 불립니다.


해당 사이트 링크는 다음과 같습니다.

https://los.eagle-jump.org/


이 사이트는 Rubiya라고 하시는 분이 제작했다고 알려져 있고 당연한 이야기일 수도 있겠지만 SQL Injection에 관심이 많다고 합니다. (운영은 stypr이라는 분이 한다고 하네요)


Rubiya라고 하는분이 해커스쿨 사이트에 올린 SQL Injection 관련 문서들도 있습니다.


초보자용 문서

http://hackerschool.org/Sub_Html/HS_Posting/?uid=42

전문가용 문서

http://hackerschool.org/Sub_Html/HS_Posting/?uid=43


SQL Injection은 간단하게 설명하자면, 웹 사이트 사용자가 입력한 값이 웹 서버로 가서, 그 값을 토대로 SQL 쿼리문을 만들고, 그 쿼리로 DBMS에 명령을 내립니다. 이때 사용자가 입력한 값이 SQL 쿼리문을 요렇게 저렇게 변형시켜서 웹 사이트 개발자가 의도하지 않은 동작을 하도록 만드는 공격 기법이 SQL Injection이라고 할 수 있습니다.


웹 어플리케이션을 개발해보신 분이라면 좀 더 이해하기 쉽겠지요? 그래서 웹 해킹을 잘하려면 웹 개발에 대하여 잘 알아야 한다는 이야기도 있습니다. 스타트업 회사에서 인턴을 하면서 웹 개발을 1년가까이 해본 저로서는 맞는 이야기라고 생각합니다. 웹 개발 경험이 많이 있거나, 웹 서비스 자체에 대한 이해도가 높을 수록 웹 해킹 기법에 대하여 알게 되었을 때 빨리빨리 습득한다고 생각합니다.


이제 Lord of SQL Injection 사이트에 대해 좀더 알아보겠습니다.


위에 알려진 링크로 이동하시면 다음과 같은 페이지가 나타납니다.



이 사이트는 IE(Internet Explorer: 인터넷 익스플로러)를 지원하지 않는다라는 이야기가 보이네요. 웹 해킹을 하시려면, 아니 웹 개발과 같은 웹 관련된 무언가를 하시려면 필수적으로 크롬 브라우저는 설치하시는 것을 추천합니다. 이유는 다양하게 있겠지만, 웹 표준을 가장 잘 준수하는 웹 브라우저이기도 하고, 성능과 보안측면에서도 뛰어나며, 여러 강력한 확장 프로그램들도 지원합니다. 크롬 브라우저에 내장된 개발자도구 역시 좋습니다.


enter to the dungeon을 눌러서 로그인 페이지를 한번 띄워보겠습니다.

아주 심플한 로그인/회원가입 창이 나타납니다. 이런 워게임 사이트에 가입을 하실 때 어느정도 눈치채신 분도 있겠지만, 일반적인 웹 서비스를 제공하는 사이트에 비해 UX가 몹시 떨어집니다. Join 버튼을 눌렀을 때, 패스워드 확인과 같은 필드가 아예 없는 것을 비롯해서, 비밀번호 찾기 등의 기능도 없습니다. 패스워드의 길이제한이라던지, 특수문자를 몇개를 넣으라던지 같은 제약사항도 없는 경우가 많습니다.


그리고 한가지 더 조언해드리는 바로는 이러한 사이트들은 보통 소위말하는 해커, 정보보안쪽에 관심이 많거나 그쪽 분야에 종사하는 분들이 만든 사이트이고 일반적으로 회원가입한 패스워드가 서버에 암호화되어 저장되지 않을 가능성이 높습니다. 다르게 말하면 여러분들이 가입할때 기입한 패스워드는 사이트 운영자가 그대로 볼 수 있다는 것이지요. 따라서 패스워드는 1234 등과 같이 털려도 상관없는 값으로 하는 것을 추천드립니다.


이렇게 회원가입 후 들어가보도록 합시다.


대충 이러한 페이지가 나타나게 된다. 저기 나타난 몬스터들이 각각 Level의 문제들을 나타내는 것이고, 몬스터 이미지를 클릭하면 해당 문제로 넘어갈 수 있다. 일단 첫번째 저 몬스터를 클릭해서 Level 1을 풀어보겠습니다.


알고리즘 공부를 제대로 해보겠다고 다짐하고난 뒤의 나에게 코드포스는 꽤 괜찮은 공부법인듯 합니다.


온라인 컨테스트가 생각보다 자주 있고, (월 6회정도라고 합니다) 대회형식으로 2시간 집중해서 문제를 풀게 되며, 추후 컨테스트가 끝난 뒤 풀이도 제공합니다. (모든 컨테스트에 대하여 모두 제공하는지는 모르겠습니다.)


또 컨테스트가 끝난 뒤 채점을 하면, 어떤 테스트 케이스에서 틀렸는지, 테스트 케이스 값도 같이 보여줍니다.


알고리즘 공부하기에는 정말 좋은 사이트라고 생각합니다.


A번 문제는 방식을 하나하나 따라가면 되는 시뮬레이션 문제였습니다.


c장의 책을 매일 읽는데 처음에는 v0페이지씩 읽고, 다음날은 v0+a페이지를 읽고, 그다음날은 v0+a+a페이지를 읽고.. 이런식으로 읽는 페이지수에 가속도가 붙다가, 그 읽는 페이지 양의 한계는 v1페이지 입니다.


그리고 다음날 읽을 때는 전날 읽은 페이지 다음부터 읽는것이 아니라 마지막 l페이지 만큼은 다시 복습을 한다고 합니다.


이러할 때 c페이지의 책을 모두 읽는데 걸리는 날 수를 구하는 문제입니다.


아래는 코드입니다.

#include <iostream>

using namespace std;

int main() {
	int c, v0, v1, a, l;
	int current_page = 0;
	int iteration = 0;
	cin >> c >> v0 >> v1 >> a >> l;

	while(true) {
		int increment = v0 + iteration * a;
		if (increment > v1) increment = v1;
		iteration++;
		current_page += increment;
		if (current_page >= c) break;
		current_page -= l;
	}
	cout << iteration << endl;
}


B번 문제는 기하문제인데, 정 n각형에서 각도 a를 구할 때 각 a와 가장 근사한 값을 가진 각도를 나타내는 꼭지점 번호를 구하는 문제입니다.


정 n각형의 그림은 다음 페이지에서 참조했습니다.

https://en.wikipedia.org/wiki/Regular_polygon



그림을 그려보면 n각형에서 나타나는 각도에 패턴이 있는데, 

5각형의 예를 들면, 저기 보이는 검정색 각도(interior angle)이 k라고 하고, 빨간색 각도를 theta라고 한다면


나타날 수 있는 각도는 다음과 같습니다.

v1 v2 v3 = k

v1 v2 v4 = k - theta

v1 v2 v5 = k - theta - theta


n의 수가 커지면 저 k - theta - theta - theta ... - theta 이런식의 경우도 나타나겠죠?


k와 theta는 n에 관한 수식으로 나타낼 수 있습니다.


위 식을 이용해서 적용하면 답을 구할 수 있습니다.


아래는 코드입니다.




#include <iostream>

using namespace std;

double ABS(double x) {
	return x*x;
}

int main() {
	int n, a;
	double k, theta;
	int ans = 3;
	double error = 9999999;
	
	cin >> n >> a;
	k = 180 - (((double)360)/n);
	theta = (((double)180)/n);
	
	double candidate = k;
	error = ABS(candidate - a);
	for (int v3 = 3; v3 <= n; v3++) {
		double cur_err = ABS(candidate - a);
		if (cur_err <= error) {
			ans = v3;
			error = cur_err;
		}
		candidate -= theta;
	}
	cout << "1 2 " << ans << endl;
}


아직은 내공이 부족해서 두 문제 밖에 풀지 못했지만, 꾸준히 풀어서 좀 더 높은 레이팅을 목표로 해보겠습니다.

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


첫번째 포스팅입니다.


한국정보올림피아드 1996년도 중등부 1번문제 연속부분최대곱 문제를 풀어봤습니다.


흔하디 흔한 dp(dynamic programming)으로 풀었습니다.


dp[i]는 i번째 수열에서 끝나는 부분수열의 최대곱을 저장합니다.


가령 arr이 원래 수열이라고 하면

arr

1.1

0.7 

1.3 

0.9 

1.4 

0.8 

0.7 

1.4 

dp

1.1 

0.77 

1.3 

1.17 

1.638 

1.3104 

0.91728 

1.4 

dp[0]은 arr[0]

dp[1]는 arr[0] * arr[1]

dp[2]은 arr[0] * arr[1] * arr[2]

dp[3]은 arr[0] * arr[1] * arr[2] * arr[3]

dp[4]은 arr[0] * arr[1] * arr[2] * arr[3] * arr[4]

dp[5]은 arr[0] * arr[1] * arr[2] * arr[3] * arr[4] * arr[5]

dp[6]은 arr[0] * arr[1] * arr[2] * arr[3] * arr[4] * arr[5]

dp[7]은 arr[7]

이 됩니다.

dp[7]의 경우를 제외하고 항상 더 곱하는것이 좋은 결과를 가져오는데, dp[6]의 경우 1보다 작으므로, dp[7]에서는 이전 앞의 누적 곱을 제외하고 본인만 가져가는것이 이득이므로 dp[7] = arr[7]이 됩니다.



출력 시, 포맷 스트링으로 %.3lf를 사용하면 소수점 4번째 자리에서 자동 반올림된 값이 나타나는 것을 처음 알았네요.


반올림 된 값으로 출력만 할 경우 유용할 것 같습니다.



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

typedef double vt;

int N;
vector<vt> arr;
vector<vt> dp;

int main() {
	cin >> N;
	
	arr.reserve(N);
	dp.reserve(N+1);
	for (int i=0; i < N; i++) {
		vt val;
		cin >> val;
		arr.push_back(val);
	}
	dp[0] = arr[0];
	for (int i=1; i <= N; i++) {
		if (dp[i-1] > 1) {
			dp[i] = dp[i-1] * arr[i];
		} else {
			dp[i] = arr[i];
		}
	}
	vt max_val = -987654321;
	for (int i=0; i < N; i++) {
		max_val = max(max_val, dp[i]);
	}
	printf("%.3lf\n", max_val);
	
}


+ Recent posts