글 최종 수정일 : 2019/11/13


개요

주변 지인에게 컴퓨터활용능력 1급(속칭 컴활) 자격증 강의를 좀 추천해달라고 질문을 받았는데 사실 잘 몰라서 이번 기회에 


좀 찾아보면서 여기에 정리해보려고 한다.


계속해서 내용을 추가 할 예정.




뭐 모든 자격증이나 시험들이 다 그렇듯이, 사람마다 기본 실력이 달라서 이미 엑셀 최고수에 전산관련 지식이 빠방하다면 


그냥 시험쳐도 붙을 수도 있고, 아니면 몇달씩 고생해도 떨어질 수도 있다.



컴활은 특히나 1급은 그래도 컴퓨터 자격증 중에서 꽤 난이도가 있는 편인데다가, 각종 여러 사기업/공기업에서 행정직무로


지원할 때 서류전형 시 가산점을 쏠쏠하게 주는 편이라서, 행정직을 목표로 한다면 따 놓으면 괜찮은 자격증 중 하나이긴 하다.









정익종 컴활 강의


보통 네이버 같은 곳에 "컴활 인강"이라고 검색하면, 광고를 빠방하게 넣은 유료인강들이 많이 등장하게 되는데, 정익종 컴활강의는 유료/무료 강의가 둘 다 있다.


당연한 이야기겠지만, 유료강의가 좀 더 양도 많고 디테일하게 잡아줄 것이다. 


하지만 무료강의만 듣고도 충분히 합격하는 사람 역시 존재할 것이다. (독학으로 붙는 사람도 있는 마당에~)



따라서 이제 소개하는 강의는 무료인강을 한번 들어보고 결정해도 되지 않을까 싶다.




정익종쌤 한방 컴활이라는 곳인데 일단 다음 카페가 있다. (링크는 하단에 첨부하였다)



들어가보니 대략 위와 같이 생겼다. 다음카페로 되어있고 딱 봐도 하나의 목적으로만 이루어진 카페같은느낌!!



일단 강의 금액을 알아보자.

좌측의 수강 금액 게시판에 가 보니, 1급 금액 / 2급 금액 글 딱 2개가 있다.


컴활 1급 강좌 수강금액을 알아보기 위해서 해당 글을 눌러보았다.



대략 위와 같은 가격이 나타나는데, 왼쪽에 무료강의라고 보이는 부분이 있어서 한번 눌러 보았다.



참고로 좌측 매뉴중에서도 아래쪽으로 쭈~~욱 내리면 무료강의 게시판이 있다. 저 링크를 클릭해서 들어와도 된다.


(아래 것은 워드강의이므로 위쪽 링크를 눌러야 한다.)



무료강의 게시판들 중에서 유투브 무료강의 관련된 글도 있는데, 들어가보면 다음과 같이 되어있다.


링크를 따라가서 유투브에서도 무료 강의를 볼 수 있다.



대충 둘러보니, 무료강의는 기출문제 해설과 개념 요약 정도의 강의가 있다.


돈을 지불하고 유료강의를 듣게 되면, 챕터별로 하나 하나 설명해주는 강의가 되는 듯 하다.



정익종 컴활 카페 링크는 다음과 같다.


http://cafe.daum.net/Compcafe


유투브 강의 링크는 다음과 같다.


https://www.youtube.com/playlist?list=PLMjRiP-gsilXrTrEQL-GEC9vHoMkXPsQs

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


백준 저지 2529번 문제, 부등호 문제이다.


문제 접근

부등호에 맞는 숫자들 배열들 중 가장 큰 값과 작은 값을 구하면 된다.

0으로 시작해도 친다고 하므로, 예외처리를 덜 귀찮게 해주는 그런 옵션이라고 보면 된다.

k가 9로 값이 작아서 10의자리 수가 나올 것이므로 완전탐색 같은걸로 풀릴 것 같은 냄새가 난다.


뭔가 이런 문제는 최대한 쉽게 구현해서 풀어버리고 싶었다. (시간복잡도 최적화따위는 통과만 할 정도면 충분하다!!) 란 마인드?!



시간복잡도와 알고리즘 계획


일단 문제를 대충읽었을때는 10자리수 0000000000 ~ 9999999999 까지 생각을 했었다.

물론 이 수를 다 체크하면 TLE도 나며, 

9,999,999,999라는 수는 일단  100억 정도 되는 수 이다.


1초에 1억번 연산을한다고 보면 100초이니까 절때 안되는 계산량이다.



근데 문제를 다시 읽어보니 0부터 9까지 수가 최대 1번씩만 나온다고 한다.


그러면 최악의 경우 10!라고 볼 수 있고 이 수는, 3,628,800이다. 360만 정도인데


각각의 수를 붙여서 10자리수 스트링을 만들고, 정수를 만들어서 체크를 하려면 체크할때마다 10의 연산량, 대충 for-loop 10번을 도니까


360만에 x 10해서 3600만 정도 된다.


약간 간당간당해진다.


(실제로 요런식으로 구현하니까 생각보다 꽤 느리더라.)


그래서 여기서 더 경우의 수를 줄이도록 프루닝(Pruning : 가지치기)를 해 보면,


숫자를 만들다가 부등호 방향에 부합하지 않으면 그 쪽은 더 이상 해 볼 가치가 없으므로 그쪽은 탐색하지 않으면 된다.


부등호 기댓값으로 대충 절반정도 컷팅이 될 것 같다.


시간복잡도는 //(O(n!)//)이 그대로 나오긴 할 것 같다.




구현 코드

구현은 일반적은 dfs를 돌리면 된다.


재귀함수를 이용해서 간편하게 작성해도 되고, 중간에 프루닝 하는 부분들이 들어가야 한다.


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
vector<string> val;
string largest, smallest;
ll large, small;
bool used[11];
int value[11];
int n;
void dfs(int pos) {
	if (pos == n + 1) {
		string tmp = "";
		ll cur_val = 0;

		for (int i = 0; i <= n; i++) {
			cur_val *= 10;
			tmp += (char)value[i] + '0';
			cur_val += value[i];
		}
		if (cur_val < small) {
			small = cur_val;
			smallest = tmp;
		}

		if (cur_val > large) {
			large = cur_val;
			largest = tmp;
		}

		return;
	}

	for (int i = 0; i < 10; i++) {
		if (used[i] == false) {
			if (pos > 0) {
				if (val[pos - 1] == "<" && value[pos - 1] > i) continue;
				if (val[pos - 1] == ">" && value[pos - 1] < i) continue;
			}
			used[i] = true;
			value[pos] = i;
			dfs(pos + 1);
			
			used[i] = false;
		}
	}
}
int main() {
	small = 9876543210;
	cin >> n;
	val.resize(n);
	for (int i = 0; i < n; i++) {
		cin >> val[i];
	}
	dfs(0);
	cout << largest << endl << smallest << endl;

	return 0;
}




English Write-up

 

I saw 'Newbie CTF' in ctftime, so I joined.

 

The ctf was held by korean hacking team.

 

I prefer web hacking challenges, so at the first I tried web problem.

There was only one web challenge.

 

When I clicked link, I can see a just simple single page.

 

There is nothing interesting, although I inspect the html source code.

 

We should enter the "normalflag.iwinv.net" link, via the page shown above.

 

 

I tried to enter the "normalflag.iwinv.net" link directly, and saw that warning documents.

 

I just typed the target host domain on the gate page,

the alert modal appeared, I assume the host string is blacklisted.

 

 

I analysis the http packet with burp suite.

When I typed "naver.com" to hostname field, 

the http request is like that.

 

response code is 302 redirection with some tailing "?secret=" query parameter.

 

I just concatenate the target host with that secret parameter, and try to connect directly, 

They said, it is not intended solving direction. Whenever we enter the host, the secret parameter value changes. So, I assume the secret parameter is a kind of one time ticket with randomly created.

 

I tried more with some url fragment(#123 like that) or tailng path like host.com/blur/blur but it doesn't work.

When I concatenated port number :80 to the target hostname, it looks working!

 

 

I got the flag!!

 

 

한글 풀이

 

Newbie CTF라는게 있길래 참여해봤다.

 

한국팀 주최 대회였다.

 

웹쟁이 답게 웹 문제부터 확인을 했는데, 한문제 밖에 없어서 조금 아쉽..

 

링크로 들어가면 간단한 페이지가 하나 나온다.

 

소스보기를 봐도 별 다른건 없는듯.

 

normalflag.iwinv.net라는 링크를 들어가야하는데, 저 페이지를 통해서 들어가야 하는 듯 하다.

 

 

그냥 링크로 들어가니 저런 경고문이 뜬다.

 

그렇다고 그냥 링크를 치고 들어가려니, 

호스트가 필터링 된 듯.

 

 

원리가 어떤식인가 싶어서 버프슈트를 한번 잡아보았다.

네이버로 해서 잡아보니,

요런 요청이 가고, 

 

302응답으로 Redirection이 되는데, 뒤에 ?secret=라는 파라메터가 붙어서 온다.

 

저기있는 secret을 띠어서 flag url에 박아서 보내보니

의도된 풀이가 아니라고 한다. secret이 요청 때 마다 바뀌는 걸로 봐서, 그때그때 발급하는 티켓 같은 방식인 것으로 보인다.

 

그래서 이것 저것 시도해보다가, 포트번호 :80을 붙여서 요청을 보내보았다.

 

되는 것 같았다.

Flag를 얻었다.

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

 

9663번: N-Queen

N-Queen 문제는 크기가 N × N인 체스판 위에 퀸 N개를 서로 공격할 수 없게 놓는 문제이다. N이 주어졌을 때, 퀸을 놓는 방법의 수를 구하는 프로그램을 작성하시오.

www.acmicpc.net

 

문제분석

백준 9663번 N-Queen 문제입니다. 굉장히 유명한 문제 중 하나입니다.

 

완전탐색으로 풀 수 있는것으로 알려져있습니다.

 

N이 15인데, 그러면 체스 말 판이 15x15=225칸이 있고, 이 중 15칸을 고른다고 하면

 

경우의 수가 //(\scriptstyle 225\textstyle C\scriptstyle 15//) 가 되는데, 뭐, 대충 계산해봐도

 

1억이라는 수를 훌쩍 넘는 것을 알 수 있습니다. //(100^{10}//)라고만 봐도 //(10^{20}//)이 되니깐, 1억이 //(10^8//)인 것을 감안하면 안되는 경우의 수가 되지요.

 

여기서 무조건 아닌 수를 조금 줄여보도록 하면,

 

퀸이 같은 행이나 열에 있으면 공격할 수 있으므로 각자 다른 열이나 행에 두어야 합니다.

 

그러면 NxN의 체스판에 퀸을 N개를 두려면 1행에 있는 퀸, 2행에 있는 퀸... N행에 있는 퀸이 최대 1개씩 밖에 있을 수 없습니다.

 

그리고 열에도 퀸은 최대 1마리만 둘 수 있기 때문에 이 조건을 맞추어 퀸을 배열하는 경우의 수는

 

1부터 N까지의 수를 한 줄로 나열하는 경우의 수와 같다고 볼 수 있습니다.

 

즉 //(N!//)이 되지요.

 

예를들어 //(N=4//)이고 //(1,3,2,4//)와 같이 배열한다는 것은 퀸을 (1,1), (2,3), (3,2), (4,4)에 배열한다는 뜻으로 볼 수 있습니다.

 

 

 

여기서 //(N=15//)일 때 //(15!=1307674368000//)인데 이 수도 여전히 큽니다.

 

하지만 퀸을 배치하다가 대각선으로 서로 공격할 수 있는 경우, 더이상 탐색하지 않는 가지치기(pruning)을 적용을 하면 저 수 보다는 적은 수의 탐색으로 경우의 수를 모두 찾아 볼 수가 있습니다.

 

구현전략

일단 완전탐색 문제이므로 제귀를 활용한 dfs로 탐색을 하면 쉽게 구현을 할 수 있습니다.

 

N개의 수를 나열하는 문제에 + 퀸이 대각선으로 공격할 수 있다는 점을 체크를 해야 합니다.

 

같은 대각선에 있는 좌표는 x좌표+y좌표의 값이 같거나, x좌표-y좌표의 값이 같습니다. 

 

이게 왜 그런가 하는지는 엑셀 등에 직접 좌표마다 값을 구해서 눈으로 보면 간단하며,

 

1차함수의 그래프를 그려봐도 마찬가지로 쉽게 알 수 있습니다.

 

x+y=k의 그래프를 그리면 2차원 좌표계에서 좌측상단에서 우측하단으로 뻗는 직선의 그래프를 확인할 수 있습니다.

x-y=k의 그래프 역시 2차원 좌표계에서 우측상단에서 좌측하단으로 뻗는 직선의 그래프를 확인할 수 있습니다.

 

이러한 점을 고려해서 제귀로 구현하면 생각보다 간단하게 구현할 수 있습니다.

 

코드

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef long long ll;
typedef pair<int, int> pii;
/*
N=15일때
좌표계가 (0,0) ~ (14,14)라고 볼때,
*/
int pl[35]; // 0 ~ 28
int mi[35]; // 14 ~ -14
bool num[15];
int val[15];
int ans;
int n;
void dfs(int x) {
	if (x == n) {
		ans++;
		return;
	}
	for (int y = 0; y < n; y++) {
		if (num[y] == false && pl[x+y] == false && mi[x-y+14] == false) {
			val[x] = y;
			num[y] = pl[x + y] = mi[x - y + 14] = true;
			dfs(x + 1);
			num[y] = pl[x + y] = mi[x - y + 14] = false;
		}
	}
}
int main() {
	cin >> n;
	dfs(0);
	cout << ans << endl;
}

코드포스 컨테스트를 치르고 나면 항상 결과가 궁금해지죠. 특히나 레이팅의 변화가 어떻게 될 것인지가 초유의 관심사 중 하나 일 것입니다.

 

Contest가 끝나고 System Test이 끝날 때 까지 기다린 뒤, 수 시간 정도 지나야지 레이팅 변화가 적용이 됩니다.

 

이를 미리 알아볼 수 있는, 즉 레이팅 변화를 예측하게 해주는 웹 사이트가 있는데요.

 

https://cf-predictor-frontend.herokuapp.com/

 

CF-Predictor

 

cf-predictor-frontend.herokuapp.com

 

위 링크로 들어간 뒤 본인이 참여한, 혹은 레이팅 변화를 예측해보고 싶은 대회를 선택한 뒤 기다리시면 됩니다.

 

참여한 모든 인원에 대한 데이터를 표로 한번에 보여주므로, 모바일에서 보는 경우 데이터 폭탄을 맞을 수도 있으니 주의 해주시면 되겠습니다.

 

 

https://codeforces.com/blog/entry/50411

 

CF-Predictor — Know your rating changes! - Codeforces

 

codeforces.com

 

해당 CF-Predictor를 만든 사람이 홍보하는 코드포스 블로그도 있습니다.

 

크롬 익스텐션으로도 있다고 하니, 관심있으면 설치해보시는 것도 좋을 것 같습니다.

 

https://chrome.google.com/webstore/detail/cf-predictor/ocfloejijfhhkkdmheodbaanephbnfhn

 

CF-Predictor

This extension predicts rating changes for Codeforces. It shows approximate deltas during and after the contest.

chrome.google.com

 

덧붙여서 코드포스 레이팅 시스템은 ELO 라는 많이 쓰이는 방식을 쓴다고 하니, 관심있으시면 아래 링크에서 좀 더 읽어보셔도 좋을 것 같습니다.

https://codeforces.com/blog/entry/20762

 

Open Codeforces Rating System [updated on October 2015] - Codeforces

 

codeforces.com

 

 

(레이팅이 떨어져서 심란한 차에 쓴 글입니다 ㅜㅜ)

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

 

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

 

 

 

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모양으로 배열해주진 않지만, 얼추 트리에 가까운 모양으로 배열해줍니다.

 

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

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

 

2751번: 수 정렬하기 2

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)이 주어진다. 둘째 줄부터 N개의 줄에는 숫자가 주어진다. 이 수는 절댓값이 1,000,000보다 작거나 같은 정수이다. 수는 중복되지 않는다.

www.acmicpc.net

위 문제의 정답코드이기도 합니다.

 

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

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

 

17825번: 주사위 윷놀이

주사위 윷놀이는 다음과 같은 게임판에서 하는 게임이다. 가장 처음에는 시작에 말 4개가 있다. 말은 게임판에 적힌 화살표의 방향대로만 이동할 수 있다. 파란색 칸에서 말이 이동을 시작하는 경우에는 파란색 화살표의 방향으로 이동해야 하며 파란색 칸을 지나가는 경우에는 빨간 화살표의 방향대로 이동해야 한다. 게임은 1부터 5까지 한 면에 하나씩 적혀있는 5면 주사위를 굴려서 나온 수만큼 이동하는 방식으로 진행한다. 이동하려고 하는 칸에 말이 이미 있는 경우에

www.acmicpc.net

문제설명

백준저지 17825번 문제, 주사위 윷놀이 풀이입니다.

 

문제를 보면 나름 복잡해 보이는 윷놀이 판이 있고, 이 윷놀이 판에서 주사위를 10번 던져서 그 주사위의 눈금값이 고정되어 있을 때, 어떻게 하면 최대 점수를 얻느냐 대략 이런문제입니다.

 

문제에서 설명이 좀 부족한 부분으로는, 윷판에 써잇는 숫자가 그 곳에 오르면 얻게 되는 점수이고, 빨간색 화살표는 기본적으로 이동하는 길입니다. 파란색의 화살표는 파란색 큰 동그라미 위치에 말이 놓였을때 이동하는 경로입니다.

 

윷놀이에 대한 기본적인 규칙은 다들 알 것이라고 생각해서, 해당 부분 설명 부분은 간단하게 한 느낌입니다.

 

 

문제 해결 전략 설정

 

일단 말이 4가지가 있고 각 10번의 이동 중 각각 이동마다 1,2,3,4번 말 중 어떤 말을 움직일지를 결정해야 합니다.

 

10번마다 독립적인 4가지의 선택지가 있고, 따라서 총 결정할 수 있는 방법은 //(4^{10}//)이 되겠습니다.

 

이 값은 //(2^{20}//)의 값과 동일하며, //((2^{10})^{2} = 1024^2 = 1048576//)입니다.

 

100만정도 되는 양으로 1초안에 충분히 계산할 수 있는 계산량입니다. 보통 1억번 연산에 1초로 생각하시면 편합니다.

 

따라서 모든 경우의 수를 다 따져보아도 충분히 풀리는 문제이므로, 완전탐색으로 해결할 수 있습니다.

 

구현전략

말의 개수가 4가지이고, 10개의 독립적인 선택지를 고르는 방식이므로, 이러한 경우 비트마스킹(bitmasking)을 이용해서 구현하면 편리합니다.

 

비트는 0과 1로 바이너리(2가지)이지만, 2비트를 사용하면 00,01,10,11로 4가지를 표현할 수 있습니다. 따라서 2*10=20 비트만 사용하면 해당 경우의 수를 모두 표현할 수 있습니다.

 

모든 경우의 수를 모두 순회하는 방식은 비트마스킹을 이용한다고 할 때, 각각의 윷놀이 말이 움직이는 것을 시뮬레이션을 해서 점수를 구해야 합니다.

 

윷 판의 형태가 규칙성을 찾기 힘든 형태이지만, 총 33개 칸에 주사위가 1~5의 숫자만 나오므로 룩업테이블(look-up table)을 사용할 만 합니다.

 

아래와 같이 그림을 그려서 룩업 테이블을 구성해 보았습니다.

 

빨간색은 칸에 부여한 번호이고, 노란색은 칸에서 얻는 점수입니다.

 

코드

구현한 코드입니다.

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

//이동 규칙이 복잡할 수 있으므로 Look-up 테이블을 만들어서 사용한다.
//jump[index][0] = 해당 판 점수
//jump[index][1~5] => 주사위 해당 수가 나오면 이동하는 양
int jump[33][6] = {
	{0,1,2,3,4,5}, //0번자리
	{2,2,3,4,5,9}, //1번자리
	{4,3,4,5,9,10}, //2번자리
	{6,4,5,9,10,11}, //3번자리
	{8,5,9,10,11,12},//4번자리
	{10,6,7,8,20,29},//5번자리
	{13,7,8,20,29,30}, //6번자리
	{16,8,20,29,30,31}, //7번자리
	{19,20,29,30,31,32}, //8번자리
	{12,10,11,12,13,14}, //9번자리
	{14,11,12,13,14,15}, //10번자리
	{16,12,13,14,15,16}, //11번자리
	{18,13,14,15,16,17}, //12번자리
	{20,18,19,20,29,30}, //13번자리
	{22,15,16,17,24,25}, //14번자리
	{24,16,17,24,25,26}, //15번자리
	{26,17,24,25,26,27}, //16번자리
	{28,24,25,26,27,28}, //17번자리
	{22,19,20,29,30,31}, //18번자리
	{24,20,29,30,31,32}, //19번자리
	{25,29,30,31,32,32}, //20번자리
	{26,20,29,30,31,32}, //21번자리
	{27,21,20,29,30,31}, //22번자리
	{28,22,21,20,29,30}, //23번자리
	{30,23,22,21,20,29}, //24번자리
	{32,26,27,28,31,32}, //25번자리
	{34,27,28,31,32,32}, //26번자리
	{36,28,31,32,32,32}, //27번자리
	{38,31,32,32,32,32}, //28번자리
	{30,30,31,32,32,32}, //29번자리
	{35,31,32,32,32,32}, //30번자리
	{40,32,32,32,32,32}, //31번자리
	{0,32,32,32,32,32} //32번자리
};
int ans;
void check(int bit) {
	int score = 0;
	int occupation[35];
	int pos[4];
	memset(occupation, 0, sizeof(occupation));
	memset(pos, 0, sizeof(pos));
	occupation[0] = 4;

	for (int turn = 0; turn < 10; turn++) {
		//이번에 옮길 말
		int horse = (bit >> (turn * 2)) & 0x3;

		//이동하는 거리
		int move = dice[turn];

		//현재 위치
		int& current_pos = pos[horse];

		//이동할 위치
		int next_pos = jump[current_pos][move];

		//이번 이동으로 얻을 점수
		int get_score = jump[next_pos][0];

		//처음이나 끝 위치가 아닌데 말이 겹치는 경우
		if (occupation[next_pos] > 0 && next_pos && next_pos != END) {
			//불가능한 이동
			return;
		}
		else {
			score += get_score;
			occupation[next_pos]++;
			occupation[current_pos]--;
			current_pos = next_pos;
		}
	}
	ans = max(ans, score);
}
int main() {
	for (int i = 0; i < 10; i++)
		cin >> dice[i];
	for (int bit = 0; bit < (1 << 20); bit++) {
		check(bit);
	}
	cout << ans << endl;

}

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

 

17822번: 원판 돌리기

반지름이 1, 2, ..., N인 원판이 크기가 작아지는 순으로 바닥에 놓여있고, 원판의 중심은 모두 같다. 각각의 원판에는 M개의 정수가 적혀있고, i번째 원판에 적힌 j번째 수의 위치는 (i, j)로 표현한다. 수의 위치는 다음을 만족한다. (i, 1)은 (i, 2), (i, M)과 인접하다. (i, M)은 (i, M-1), (i, 1)과 인접하다. (i, j)는 (i, j-1), (i, j+1)과 인접하다. (2 ≤ j ≤ M-1) (1, j)는 (

www.acmicpc.net

문제 개요

백준저지 17822번 문제 원판 돌리기 문제 풀이입니다.

 

규칙에 따라 원판을 돌리고, 인접 수를 지우고, 수를 변경하고 하는 식의 문제입니다.

 

하라는대로 하면 되는 시뮬레이션 문제에 해당합니다.

 

N, M, T값이 50으로 작은편이므로, 원판을 죄다 돌려버리고 돌릴때 마다 다 체크를 한다고 해봅시다.

 

1번 돌릴때 마다 인접한 원판 수들을 체크를 해야하는데, 최대 원판에 쓰인 수의 개수는 N * M개 이므로 최대 50*50 = 2500개입니다.

 

2500개의 수를 50번 확인한다고 하면 125,000번으로 12만 5천번 정도됩니다.

 

그리고 그 때 마다 원판위의 숫자들을 shift한다고 하면, 모든 원판의 수를 다 shift하고 그 양이 50과 같이 큰 값이라 하면

 

50^4 = 6,250,000으로 625만번입니다. shift하는 것을 한번씩 여러번 반복하는 식으로 매우 대충 구현한다는 가정하에 대충 이정도 값이 나옵니다.

 

그런데 shift하는 것은 한번에 점프뛰는 방식으로도 가능하기 때문에 이정도까지도 안나옵니다.

 

고로 결론은, 그냥 그대로 따라하는 시뮬레이션 방식으로 구현해도 충분히 시간안에는 들어오는 크기의 N,M,T 값이라는 것입니다.

 

(대충 1억번 연산에 1초 정도 걸린다고 봅니다.)

 

구현 전략

원판 돌리는 연산을 간편하게 하기 위해서 bias라는 배열을 따로 두고, [원판][위치] 의 값을 읽거나 쓸 때에는 별도의 getter, setter 함수를 만들어서 코드를 작성해 보았습니다.

 

이녀석의 원리를 조금 설명드려 보자면 예를들어 다음과 같은 원판이 있다고 칩시다. 한 원판에 숫자가 4개 쓰여있으니 M=4가 됩니다.

12시 부터 시계방향으로 숫자를 읽는다면 1,2,3,4 가 되겠지요?

 

이 원판을 시계방향으로 90도 돌리면 다음과 같은 모양이 됩니다.

 

마찬가지로 12시 부터 읽는다면 4,1,2,3이 됩니다.

 

이를 잘라서 평평하게 핀다고 생각해봅시다.

 

그러면 초기의 1,2,3,4는 다음 모양과 같게 나옵니다.

그리고 시계방향으로 90도 돌린 4,1,2,3은 다음과 같게 나옵니다.

실제로 C언어 배열에는 위와 같이 연속된 메모리에 값이 저장이 되겠지요. 근데 그렇다면 한번 원판을 돌릴 때 마다 4개(M개)의 칸의 값을 모두 바꾸어 주어야 하는데, 발상을 조금 바꾸어봅시다.

 

값은 맨 처음 그대로 1,2,3,4로 저장을 하되, 읽는 순서만 바꾸는 거죠.

 

처음 1,2,3,4가 나오기 위해서는 인덱스 읽는 순서를 0,1,2,3으로 읽고, 시계방향으로 90도 돌린 경우는 인덱스를 3,0,1,2의 순서로 읽는것입니다.

그러면 아까처럼 값을 바꾸는 경우와 똑같은 값이 나오게 됩니다.

 

마찬가지로, 반시계방향으로 90도 돌린 경우는 인덱스를 1,2,3,0의 순서로 읽게 되겠지요?

 

따라서 원판이 시계방향으로 돌리게 되면 bias값에 -1을 해 줄것이고, 반시계 방향으로 돌리게 되면 bias값에 +1를 해줍니다.

 

시계방향으로 한번 돌리고 반시계 방향으로 한번 돌리면 결론적으로 원래 자리로 돌아오게 되는데 bias값에는 이러한 점도 적용이 되게 됩니다.

 

즉 bias값은 맨 처음 값을 몇 번째 인덱스부터 읽을 것인지를 나타내게 되는 것이지요.

 

하지만 bias값이 4보다 크거나 음수가 나타날 수 있으니, 이 bias값이 0이상 3이하(M-1)의 값만 나오게 하도록 모듈러(나머지)연산을 해 줍니다.

 

음수의 경우 모듈러 연산을 해도 음수가 나오기 때문에 4(M값)만큼을 더해서 0이상 3이하의 값이 나오게 되는 것입니다.

 

 

 

디버깅을 위해서 dump함수도 작성했는데, 활성화 하기 위해서는 dump 함수의 return 값을 제거하고 사용하시면 됩니다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define DEL 99999
int num[55][55];
int bias[55];
int N, M, T;
typedef pair<int, int> pii;
void dump(const char[]);
int get_num(int i, int j) {
	//i번째 원판의 j번째 위치 녀석 값 가져오기
	j += bias[i];
	while (j < 0) {
		j += M;
	}
	j %= M;
	return num[i][j];
}
void set_num(int i, int j, int v) {
	//i번째 원판의 j번째 위치 녀석 값을 v로 셋팅
	j += bias[i];
	while (j < 0) {
		j += M;
	}
	j %= M;
	num[i][j] = v;
}
void process() {
	//인접수 날리고, 값 조정 한번에 하는 함수
	bool changed = false;
	vector<pii> del; //지울 수 좌표들 모음
	del.clear();

	//같은 원판 사이에 인접한 수들 찾아내기
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			int a = get_num(i, j);
			int b = get_num(i, j + 1);
			if (a != DEL && b != DEL && a == b) {
				del.push_back({ i,j });
				del.push_back({ i,j + 1 });
				changed = true;
			}
		}
	}
	//다른 원판 사이에 인접한 수들 찾아내기
	for (int i = 0; i + 1 < N; i++) {
		for (int j = 0; j < M; j++) {
			int a = get_num(i, j);
			int b = get_num(i + 1, j);
			if (a != DEL && b != DEL && a == b) {
				del.push_back({ i, j });
				del.push_back({ i + 1, j });
				changed = true;
			}
		}
	}

	//지울 녀석들 모아서 지우기
	for (auto p : del) {
		set_num(p.first, p.second, DEL);
	}
	dump("지운뒤");

	//지운 수가 없으면 계속 진행
	if (changed) return;

	int cnt, sum;
	cnt = sum = 0;
	//지워지지 않은 수와, 그 수들의 총합 을 구함
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (num[i][j] != DEL) {
				cnt++;
				sum += num[i][j];
			}
		}
	}

	//남은 수들이 평균보다 크면 -1, 작으면 +1 시킴.
	//나누기를 안쓰는 이유는 정수 소수점 잘리는 부분 때문
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (num[i][j] != DEL) {
				if (num[i][j] * cnt > sum) {
					num[i][j]--;
				}
				else if (num[i][j] * cnt < sum) {
					num[i][j]++;
				};
			}
		}
	}
	dump("덧뺄샘이후");
}
int main() {
	cin >> N >> M >> T;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			cin >> num[i][j];
		}
	}
	while (T--) {
		//회전 시킬 값들 받기
		int x, d, k;
		cin >> x >> d >> k;
		int v = x;
		int shift = k * (d == 0 ? -1 : +1);
		dump("돌리기전");
		while (v <= N) {
			//bias 배열에다가 값을 더하는식으로, 회전시킴
			bias[v-1] += shift;
			v += x;
		}
		dump("돌린뒤");
		//인접수 지우고 하는 처리들을 여기서 함
		process();
	}

	//남은 수 다 더해서 출력
	int ans = 0;
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (num[i][j] != DEL) {
				ans += num[i][j];
			}
		}
	}
	cout << ans << endl;

}
void dump(const char str[]) {
#ifndef WIN32
	return;
#endif
	puts(str);
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			cout << get_num(i, j) << ' ';
		}
		cout << endl;
	}
	cout << "============================" << endl;
}

이번 주말에는 아무것도 없는 줄 알았다가, 킥스타트 Remind 메일을 보고 토요일 밤에 구글 킥스타트가 있는것을 알고 참여했다.

 

구글 킥스타트는 구글 코드잼과 비슷한 대회인데, 다른 점은 각각의 라운드가 독립적인 하나 뿐이라는 것.

 

구글 코드잼은 예선전(Qualification Round)부터 Round 1,2,3이후 월드파이널 온사이트 대회로 넘어가는 방식으로 각 단계에서 사람수를 줄여가는 식의 서바이벌 방식의 대회라고 볼 수 있다.

 

킥스타트는 대학생들 대상으로 채용을 목적으로하는 대회로, 각각의 독립 라운드가 날짜별로 시간대별로 포진되어있다.

 

A,B,C 등등과 같은 방식으로 라운드를 구분한다.

 

따라서 하려는 라운드가 우리 한국시각으로는 새벽이더라도, 다른 한국시각으로 낮이나 저녁시간대인 다른 대회를 참여하면 되는 것이다.

 

결론부터 말하면 385등으로 총 3문제 중, 2.5문제를 풀었다.

 

뭔가 거의 다 푼 것 같아서 기분은 좋다.

 

3번째 문제 Hidden Test case는 날려먹엇는데, 풀이가 올라왔으니 차근차근 읽어봐야 할듯.

 

 

1번 문제 Book Reading은 대충 설명해보면, 배수를 찾는 문제이다.

1~N 페이지인 책 중 M개의 페이지가 찢겨져 나가있고, Q명의 독자는 각각 자신이 원하는 수의 배수에 해당하는 책 페이지만 읽는다 했을때, 독자들이 읽은 페이지의 총 개수를 구하면 된다.

 

책이 찢겨지지 않았다고 했을때는, 배수만 구해서 죄다 더해버리면 된다. 다만 찢긴 페이지를 어떻게 처리하냐가 관건이다.

 

 

Visible을 맞추기 위해서는 각각 독자별로 찢겨진 페이지에 얼마나 해당되는지만 체크하면 된다. Brute Force로 풀리는 크기로 N,M,Q가 1000이하이다.

 

Visible을 맞추기 위한 코드이다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
ll solve() {
	int N, M, Q;
	scanf("%d%d%d", &N, &M, &Q);
	ll ans = 0;
	vector<int> torn(M);
	vector<int> reader(Q);
	for (int i = 0; i < M; i++) {
		cin >> torn[i];
	}
	for (int i = 0; i < Q; i++) {
		cin >> reader[i];
		ans += (ll)N / reader[i];
	}

	for (auto tt : torn) {
		for (auto rdr : reader) {
			if (tt % rdr == 0) {
				ans--;
			}
		}
	}
	return ans;
}
int main() {
	int tc;
	scanf("%d", &tc);
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: %lld\n", t, solve());
	}
}

 

Hidden을 맞추기 위해서는 좀 더 효율적인 코드가 필요하다.

 

찢겨진 페이지들을 소인수 분해한 뒤, 약수를 모두 다 구해서 해당 약수들에 cnt배열 값을 1씩 증가시키는 방식으로 독자를 쿼리를 최적화 해 보았다. 될지 안될지는 긴가 민가 했는데, 대충 통한듯.

 

소수들은 에라토스테네스의 채를 이용해서 구했다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define MAXN 100005
bool sieve[MAXN]; //false == prime
int cnt[MAXN];
vector<int> primes;
bool is_prime(int n) {
	if (n < 2) return false;
	if (n == 2) return true;
	for (int i = 2; i*i <= n; i++) {
		if (n % i == 0) return false;
	}
	return true;
}
void get_sieve() {
	sieve[0] = sieve[1] = true;
	for (int n = 2; n < MAXN; n++) {
		if (sieve[n] == false && is_prime(n)) {
			int base = n * 2;
			while (base < MAXN) {
				sieve[base] = true;
				base += n;
			}
		}
	}
	for (int i = 2; i < MAXN; i++) {
		if (sieve[i] == false) {
			primes.push_back(i);
		}
	}
}
void factorize(int n, vector<pair<int, int>>& factors) {
	while (n > 1) {
		for (int prime : primes) {
			if (prime > n) break;
			int cnt = 0;
			while (n % prime == 0) {
				n /= prime;
				cnt++;
			}
			if (cnt) {
				factors.push_back(make_pair(prime, cnt));
			}
		}
	}
}
void get_divisors(int n, vector<pair<int, int>>& factors, int k) {
	if (k == (int)factors.size()) {
		cnt[n]++;
		//cout << "got divisor = " << n << endl;
		return;
	}
	get_divisors(n, factors, k + 1);
	int exp = factors[k].second;
	for (int dec = 1; dec <= exp; dec++) {
		factors[k].second -= dec;
		n /= factors[k].first;
		get_divisors(n, factors, k + 1);
		factors[k].second += dec;
	}
	n *= pow(factors[k].first, exp);
	

}
ll solve() {
	memset(cnt, 0, sizeof(cnt));
	int N, M, Q;
	scanf("%d%d%d", &N, &M, &Q);
	ll ans = 0;
	vector<int> torn(M);
	vector<int> reader(Q);
	for (int i = 0; i < M; i++) {
		cin >> torn[i];
	}
	for (int i = 0; i < Q; i++) {
		cin >> reader[i];
		ans += (ll)N / reader[i];
	}
	for (int i = 0; i < M; i++) {
		vector<pair<int, int>> factors;
		factors.clear();
		factorize(torn[i], factors);
		get_divisors(torn[i], factors, 0);
	}

	for (auto rdr : reader) {
		ans -= cnt[rdr];
	}
	return ans;
}
int main() {
	get_sieve();
	int tc;
	vector<pair<int, int>> factors;

	scanf("%d", &tc);
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: %lld\n", t, solve());
	}
}

 

2번 문제는 약간 수학적인 문제같이 생겻는데, N개의 A숫자가 있고 숫자 M이 있을때

(A1 ^ k) + (A2 ^ k) + ... + (An ^ k) <= M을 만족하는 최대 k를 구하면 된다.

 

Visible은 모든 k에 대하여 다 해보고 최대 값을 구하는 완전탐색으로 풀린다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
ll A[1003];
int bitmap[20];
ll solve() {
	memset(bitmap, 0, sizeof(bitmap));
	ll ans = -1;
	int N;
	ll M;
	cin >> N >> M;
	ll sum = 0;
	for (int i = 0; i < N; i++) {
		cin >> A[i];
	}

	for (ll k = 0; k <= 1000; k++) {
		ll ss = 0;
		for (int i=0; i < N; i++) {
			ll& a = A[i];
			ss += (a^k);
			if (ss > M) break;
		}
		if (ss <= M) {
			ans = max(ans, k);
		}
	}
	return ans;
	
	
}
int main() {
	int tc; cin >> tc;
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: %lld\n", t, solve());
	}
}

 

 

Hidden은 좀더 효율적인 방법이 필요한데, 어차피 10^16이면 50비트 남짓이라는 것을 이용해서, 받는 A 숫자들을 비트별로 개수를 다 세어놓는다.

 

해당 비트 사용수가 과반수이면, 즉 N/2보다 크면, k의 해당 비트를 1로 해서 비트 들을 Inverse시켜주는 식으로 한번 전처리 한뒤,

 

M과 지금 다 합친 값과 여유분을 계산을 해서 , k의 가장 큰 bit를 xor해서 inverse해줘도 되는 경우 bit를 1로 set하는 식으로 구해보았다.

 

멍청하게 bitmap 배열을 20으로 잡아서(10^20을 생각) 런타임 에러가 막 났었는데 2^50이니 50이상 잡았어야 했다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
ll A[1003];
int bitmap[60];
ll solve() {
	memset(bitmap, 0, sizeof(bitmap));
	ll ans = 0;
	int N;
	ll M;
	cin >> N >> M;
	ll sum = 0;
	for (int i = 0; i < N; i++) {
		cin >> A[i];
		sum += A[i];
		for (int j = 0; j <= 50; j++) {
			if (A[i] & (1LL << j)) {
				bitmap[j]++;
			}
		}
	}

	for (int i = 0; i <= 50; i++) {
		if (bitmap[i] * 2 >= N) {
			ans |= (1LL << i);
			sum += (1LL << i) * (N - bitmap[i] * 2);
		}
	}
	//if (sum > M) return -1LL;

	for (int i = 50; i >= 0; i--) {
		if ((1LL << i) & ans) continue;
		ll diff = M - sum; //여유분
		ll cost = (1LL << i) * (N - bitmap[i] * 2);
		if (cost <= diff) {
			ans |= (1LL << i);
			sum += cost;
		}
	}
	if (sum > M) return -1LL;
	return ans;

}
int main() {
	int tc; cin >> tc;
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: %lld\n", t, solve());
	}
}

 

 

3번 문제는 교대근무를 하는 2명의 직원이 있고, 각 근무시간은 최소 한명이상 근무해야하며, 근무 시 얻는 Happiness를 합한 값이 각각 H를 넘는 경우의 수를 구하는 그런문제다.

 

문제를 딱 보고 어렵다 생각했는데 Visible N값이 12로 완전탐색을 조져도 될 것이라는 생각이 들었다.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
ll A[22], B[22];
ll solve() {
	ll ans = 0;
	int N;
	ll H;
	cin >> N >> H;
	for (int i = 0; i < N; i++) {
		cin >> A[i];
	}
	for (int i = 0; i < N; i++) {
		cin >> B[i];
	}
	ll AH, BH;
	for (int bit = 1; bit < (1 << N); bit++) {
		AH = 0;
		//BH = 0;
		for (int i = 0; i < N; i++) {
			if (bit & (1 << i)) {
				AH += A[i];
			}
			
		}
		if (AH < H) continue;
		
		
		for (int bbit = 1; bbit < (1 << N); bbit++) {
			BH = 0;
			for (int i = 0; i < N; i++) {
				if (bbit & (1 << i)) {
					BH += B[i];
				}
			}
			if (BH < H) continue;
			if ((bit | bbit) != ((1 << N) - 1)) continue;
			ans++;
		}
		
	}
	return ans;
}
int main() {
	int tc;
	cin >> tc;
	for (int t = 1; t <= tc; t++) {
		printf("Case #%d: %lld\n", t, solve());
	}
}

 

Hidden으로 N=20은 도저히 어떻게 해야할 지 생각이 안나서 스킵해놓은 상태이다. 이따가 풀이보고 공부해봐야겠다.

+ Recent posts