191006 백준 알고리즘 문제풀기

|

[14890번] 경사로

  • 난이도: 상
    • 전형적인 시뮬레이션 문제로, 푸는데 2시간정도 소요됐다. 시험에 나왔으면 틀렸다.
      • 문제 풀이 떠올리는데 1시간, 구현에 1시간..
      • 구현은 정말 쉬웠지만, 풀이를 나누고 case 분류하는데 정말 애를 많이 먹엇다.
    • 시뮬레이션 문제의 경우, 한 번에 모두 해결하려면 정말 답도 없다.
      • 문제를 작게 쪼개고, case를 잘 분류해서 하나하나 해결해가야 풀 수 있을 것 같다.
    • 처음에 문제를 보자마자 쉽겠지 하고 무턱 시작했다가 큰 낭패를 봤다.
    • 시뮬레이션 문제를 풀 때는
      • 우선, 문제를 작게 나누고(가능한 상황으로)
      • 나눠진 문제에 대해 조건을 확인하고
      • 알고리즘으로 구현해야 한다.
  • 문제
    • https://www.acmicpc.net/problem/14890
    • NxN 맵과 경사로 길이가 주어질 때, 경사로를 두고 해당 맵을 가로, 세로로 건널 수 있는 경우의 수를 출력한다.
      • 하나의 알고리즘으로 두 개의 회전된 맵을 만들고, 이를 이용해 풀면 된다.
  • 풀이
    • 우선, 한 줄의 가로 배열에 대한 풀이만 해결하면 문제를 풀 수 있다.
      • 이를 세로로 쌓고, 가로로 돌려서 적용하는 식으로 전체에 가로/세로 방향의 경우의 수를 모두 따질 수 있음
    • case 분류
      • 다음 칸과 높이가 같은 경우
      • 다음 칸이 높고, 1 차이나는 경우
      • 다음 칸이 낮고, 1 차이나는 경우
      • 외의 경우는 무조건 건널 수 없다(break)
    • 시뮬레이션 문제에서 제일 중요한건 case를 분류하는것이다!
      • 분류된 case를 토대로 조건에 맞게 완성시켜 나가면 되기 때문
#pragma warning(disable:4996)
#include <iostream>
#include <queue>
#include <tuple>
#include <cmath>
#include <algorithm>

using namespace std;

int map_a[100][100]; // 가로 맵 저장용
int map_b[100][100]; // 세로 맵 저장용

bool flat(int arr[], int s, int d) // 인덱스 s부터 d까지 평평한가(그래야 경사로를 둘 수 잇음)
{ // s: start index, d: dest index
	int cnt = 0;
	for (int i = s; i < d; i++)
	{
		if (arr[i] - arr[i + 1] != 0)
		{
			cnt++;
		}
	}
	if (cnt == 0)
		return true; // 평평하면 true 반환
	else
		return false; // 아니면 false 반환
}

bool stair(int arr[], int s, int d) // s부터 d까지 계단 세울 수 있는가 (계단이 존재하지 않아야 함)
{
	int cnt = 0;
	for (int i = s; i <= d; i++)
	{
		if (arr[i] == 2)
			cnt++;
	}
	if (cnt == 0)
		return true;
	else
		return false;
}


void make_stair(int check[], int s, int d) // 계단을 세운다.
{
	for (int i = s; i <= d; i++)
	{
		check[i] = 2;
	}
}

bool checker(int arr[], int w, int l)
{
	int check[100]; // check 배열엔 지나간 경우는 1, 경사로 세운 경우는 2, 못 지나간 경우는 0으로 표시
	std::fill_n(check, w, 0); // check배열 0으로 초기화

	for (int i = 0; i < w; i++)
	{ // 전체 배열에 대해서
		if (i == w - 1)
		{ // 마지막 인덱스까지 왔다면 앞 인덱스의 조건은 모두 통과한 경우다.
			check[i] = 1; // 따라서 1로 채운다.
			break;
		}
		if (arr[i] == arr[i + 1])
		{//다음칸과 높이가 같은 경우
			if (check[i] == 0) // 만약 현재 위치에 경사로가 존재하지 않는다면
				check[i] = 1; // 지나간다.
			else
				continue; // 경사로가 존재한다면 넘어간다.(경사로 중복 방지)
		}
		else if (arr[i] < arr[i + 1] && arr[i + 1] - arr[i] == 1)
		{//다음칸이 높고 1 차이나는 경우
			int sx = i - l + 1; // 경사로 처음 좌표
			int dx = i; // 경사로 마지막 좌표
			if (0 <= sx && flat(arr, sx, dx) && stair(check, sx, dx))
			{//이전에 길이가 충분하고, 경사로 구간이 평평하며, 경사로 구간에 다른 경사로가 존재하지 않다면
				make_stair(check, sx, dx);//해당 구간 경사로를 만든다.
			}
			else break; // 아니라면 break(뒤 쪽 볼 필요 없음)
		}
		else if (arr[i] > arr[i + 1] && arr[i] - arr[i + 1] == 1)
		{//다음칸이 낮고 1 차이나는 경우
			int sx = i;
			int dx = i + l;
			if (dx < w && flat(arr, sx + 1, dx) && stair(check, sx + 1, dx))
			{//다음에 경사로를 둘 길이가 충분하고, 경사로 구간이 평평하며, 경사로 구간에 다른 경사로가 존재하지 않다면
				check[i] = 1; // 현재 위치를 1로 초기화(지나갔으므로)
				make_stair(check, sx + 1, dx); // 경사로를 둔다.
				i = dx - 1; // index를 경사로 마지막 위치로 초기화한다.(중복으로 for문 돌을 필요 없음)
			}
			else break; // 아니라면 break(뒤쪽 볼 필요 없음)
		}
		else // 아닌 경우 무조건 갈 수 없음
			break;
	}
	
	//갈 수 있는지 check
	bool go = true;
	for (int i = 0; i < w; i++)
	{
		if (check[i] == 0)
			go = false; // 만약 끝까지 못간경우 false반환
	}

	return go;
}

int main()
{
	int N, L;
	scanf("%d %d", &N, &L);
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			scanf("%d", &map_a[j][i]); // 가로 방향 입력
			map_b[i][j] = map_a[j][i]; // 세로 방향 초기화
		}
	}

	int ans = 0;
	for (int j = 0; j < N; j++)
	{
		ans += checker(map_a[j], N, L); // 가로 방향 계산
	}
	for (int j = 0; j < N; j++)
	{
		ans += checker(map_b[j], N, L); // 세로 방향 계산
	}

	printf("%d\n", ans);
	
	//system("pause");
	return 0;
}

[15685] 드래곤 커브

  • 난이도: 중
    • 시뮬레이션 문제로, 풀이를 떠올리는데는 30분도 걸리지 않았지만 구현하면서 부딫히는 문제들, 그리고 조건들을 고려하느라 총 2시간 정도 소요.
    • 문제 자체를 이해하는데 많은 시간이 소요되었다.
    • 문제를 나누고, 구현했으며, 문제를 나누는 과정 및 풀이를 떠올리는 과정에서 만약 잘못된 길로 빠지면 답도 없다.
      • 특히 시뮬레이션문제는 더욱 더 그런듯 하다.
    • 다시한번 느끼지만, 시뮬레이션 문제는
      • 문제를 정확하게 이해하고
      • 모든 조건을 정확하게 파악하고
      • 문제를 제대로 쪼개는 것
    • 위 세가지가 가장 중요한 듯 하다.
  • 문제
    • https://www.acmicpc.net/problem/15685
    • x, y, d, g 순으로 주어지는 드래곤 커브가 있다.
      • x, y는 시작점 좌표, d는 방향, g는 몇 번 회전하면서 이어갈지를 의미한다.
      • d는 0, 1, 2, 3 순으로 오른쪽, 위, 왼쪽, 아래로 매핑된다.
      • g 동안 이어붙여지며 맵이 늘어난다.
    • 전체 맵의 크기는 100x100 이다.
  • 풀이
    • 문제를 나누면 아래와 같다.
      • 우선 시작 선을 긋는다.
        • 처음 포인트와 방향에 대한 선분을 긋는다.
      • 시작 섬을 기준으로 끝점을 정의한다.
        • 끝 점을 기준으로 맵이 회전하게 되며
        • 시작점을 기준으로 회전된 맵이 와서 붙는다.
      • 맵을 회전시킨다.
        • g 동안 회전시키며 위의 규칙들을 따른다.
      • 완성된 맵 상의 네모칸 갯수를 따진다.
  • 주의
    • memset(array, number, sizeof(array)) 는 1차원 배열에서도 사용 가능하지만 2차원에서도 가능하다.
    • 사용 시 반드시 #include <cstring>을 추가한다!(안그러면 런타임 에러 뜸)
    • 앞으로는 그때그때 include하는 헤더를 따로 따로 지우고 필요에 따라 불러쓰는 연습을 해야겟다.
    • 인덱스는 큰 차이가 없다면 1칸정도 넉넉히 잡는게 좋은듯 하다.
      • 본 문제에서도 100 딱 맞춰 했는데 알고보니 101까지 선언해야 문제 조건을 만족한다.
#pragma warning(disable:4996)
#include <iostream>
#include <queue>
#include <tuple>
#include <cmath>
#include <algorithm>
#include <cstring>

using namespace std;

int map[101][101]; // 맵 저장용
int check[101][101]; // 맵 중간 계산용 (동일 좌표 내에서)
int temp[101][101]; // 맵 중간 계산용 (회전시)

int dx[] = { 1,0,-1,0 }; // 각각 0, 1, 2, 3에 대한 이동 매핑
int dy[] = { 0,-1,0,1 };

int answer = 0; // 정답

vector<tuple<int, int, int, int>> dots; // x, y, d, g 저장용
void rotate(int &sx, int &sy, int &cx, int &cy, int N); // 회전 함수
void calc_map(int n); // 점수 계산 함수

void make_map(int n)
{
	for (int i = 0; i < dots.size(); i++)
	{ // 각 x, y, d, g 좌표에 대해
		memset(check, 0, sizeof(check)); // check 배열 초기화
		memset(temp, 0, sizeof(check)); // temp 배열 초기화

		int sx, sy, d, g;
		tie(sx, sy, d, g) = dots[i]; // x, y, d, g 저장
		map[sy][sx] = 1; // 최종 저장 맵용 시작좌표 check
		check[sy][sx] = 1; // 이번 turn 계산용 시작좌표 check

		int nx, ny;
		nx = sx + dx[d]; // 마지막 좌표 저장
		ny = sy + dy[d];
		if (0 <= nx && nx < n && 0 <= ny && ny < n)
		{ // 마지막 좌표가 범위를 벗어나지 않는다면
			map[ny][nx] = 1; // 최종 저장 맵용 check
			check[ny][nx] = 1; // 이번 turn 계산용 check
			while (g--)
			{ // 회전 횟수만큼 회전
				rotate(sx, sy, nx, ny, n); // 회전
			} // 위 함수에서 while 문 내의 nx, ny가 초기화됨.(회전중심점)
		}
		else continue;
	}
}

void calc_map(int n)
{
	int sum = 0;
	for (int j = 0; j < n - 1; j++)
	{
		for (int i = 0; i < n - 1; i++)
		{
			sum = 0;
			for (int y = 0; y < 2; y++)
			{
				for (int x = 0; x < 2; x++)
				{
					if (map[j + y][i + x] > 0)
					{
						sum++; // 해당 좌표에 값이 저장되어있으면 더하고
					}
				}
			}
			if (sum >= 4)
			{
				answer++; // 4 이상 저장되었으면 정답
			}
		}
	}
}

void rotate(int &sx, int &sy, int &cx, int &cy, int N)
{
	int rx, ry;
	int nsx, nsy;
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			temp[i][N - 1 - j] = check[j][i]; // 오른쪽으로 90도 회전한다.
			if (j == cy && i == cx)
			{ // 만약 현재 인덱스가 회전 중심점이라면
				rx = N - 1 - j; // 변경된 회전 중심점을 각각 rx, ry에 저장
				ry = i;
			}
			if (j == sy && i == sx)
			{ // 만약 현재 인덱스가 시작점이라면
				nsx = N - 1 - j; // 변경된 시작점을 저장
				nsy = i; // 변경된 시작점은 다음 generation이 붙는데 사용됨
			}
		}
	}

	int dx, dy; // 이동 자리 값
	dx = cx - rx;
	dy = cy - ry;
	nsx += dx; // 변경 시작점에 이동값을 더해서 최종 완성
	nsy += dy;

	//원래에 붙이기
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			int ny = j + dy;
			int nx = i + dx;
			if (0 <= nx && nx < N && 0 <= ny && ny < N)
			{ // 범위를 벗어나지 않는다면
				if (check[ny][nx] == 0 && temp[j][i] != 0)
				{
					check[ny][nx] += temp[j][i]; // 현재 좌표 계산용 check맵에 초기화
					map[ny][nx] += temp[j][i]; // 결과값용 map도 초기화
				}
				else
				{ // 범위 벗어나지 않는다면 넘어간다.
					continue;
				}
			}
		}
	}
	cx = nsx; // 회전 중심점을 새로운 중심점으로 초기화
	cy = nsy;
}


int main()
{
	int N;
	scanf("%d", &N);
	int x, y, d, g;
	for (int i = 0; i < N; i++)
	{
		scanf("%d %d %d %d", &x, &y, &d, &g);
		dots.push_back(make_tuple(x, y, d, g));
	}

	make_map(101); // 맵 만들고
	calc_map(101); // 정답 계싼
	
	printf("%d\n", answer);

	//system("pause");
	return 0;
}
  • 어렵진 않지만 시간이 너무 오래 걸린다.

191005 백준 알고리즘 문제풀기

|

[14502번] 연구소

  • 난이도: 중상
    • 바이러스가 퍼지는것까지는 bfs로 쉽게 구현하는 줄 알았는데, 벽을 랜덤하게 세우는데서 애를 먹었다.
      • 함수의 재귀적 구성을 통해 벽을 세움
    • 또한, 계산과정에서 계속 올바르게 벽이 세워져도 bfs에서 제대로 동작하지 않는 경우가 생겼다
      • 이는 연산용 별도의 map을 할당해서 해결했음
    • 전체 푸는데 2시간정도 소요된듯 하다..
      • 문제풀이 떠올리는데 30분도 안걸렸지만, 구현 및 디버깅에서 엄청나게 소모됨
  • 문제
    • https://www.acmicpc.net/problem/14502
    • 크기가 NxM인 연구소가 빈칸 0, 벽 1, 바이러스 2로 주어지고, 임의의 벽 3개를 세웠을 때 바이러스를 막을 수 있는 최대 공간을 출력한다.
    • 바이러스는 상하좌우로 1칸씩 번져간다.
    • 벽을 어떻게 세워야 하지.. 고민하다가, 결국 답은 모두 다 시도해보는 브루트 포스 문제인 것을 알았다.
      • 연산량이 많아 가능할까 생각해봤지만, 결국 선택 가능한 경우의 수는 worst case인 64*63*62의 경우의 수로 100만이 되지 않는다.
      • 따라서 모든 경우의 수를 시도 해 볼 수 있음
  • 풀이
    • 문제를 벽 세우기, 바이러스 퍼치기, 빈 공간 계산해서 최대공간 저장
    • 순으로 3단계로 나눠서 해결했다.
    • 벽 세우기
      • 함수의 재귀적 구성으로 맵의 빈 공간의 모든 곳에 일일히 벽을 세워보고 값을 비교하도록 모든 경우의 수를 돌음
    • 바이러스 퍼치기
      • 일반적인 BFS로 구현
    • 빈 공간 계산
      • for문으로
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <tuple>
#include <queue>

using namespace std;

int map[8][8]; // 입력 맵
int check[8][8]; // 처음의 원본 check 저장용
int answer = 0;

int wallmap[8][8]; // 맵에 벽을 세웠을 때 저장용
int calcmap[8][8]; // 맵에 벽을 세웠을 때 연산용

vector<pair<int, int>> virus; // 바이러스 좌표 저장용

int dx[] = { -1,1,0,0 }; // 바이러스 입력 방향
int dy[] = { 0,0,-1,1 };

void init(int w, int h) // 벽 세웠을 때 저장용 맵을 원본 맵으로 초기화
{
	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			wallmap[j][i] = check[j][i];
		}
	}
}

void calcinit(int w, int h) // 계산용 맵을 벽 세워진 맵으로 초기화
{
	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			calcmap[j][i] = wallmap[j][i];
		}
	}
}

void spread(int w, int h) // 바이러스 퍼치기
{
	queue<pair<int, int>> q;

	calcinit(w, h); // 계산용 맵을 완성된 맵으로 초기화

	for (int j = 0; j < virus.size(); j++)
	{//모든 바이러스 좌표에 대해
		int x = virus[j].first;
		int y = virus[j].second;

		calcmap[y][x] = 2;
		q.push(make_pair(x, y));
		while (!q.empty())
		{
			int cx = q.front().first;
			int cy = q.front().second;
			q.pop();
			for (int i = 0; i < 4; i++)
			{
				int nx = cx + dx[i];
				int ny = cy + dy[i];
				if (0 <= nx && nx < w && 0 <= ny && ny < h)
				{
					if (calcmap[ny][nx] == 0)
					{ // 계산용 맵에 바이러스가 퍼질 수 있는 다음 위치는 빈 공간(0)만 가능함
						calcmap[ny][nx] = 2; // 바이러스가 없다면 바이러스 퍼친다.
						q.push(make_pair(nx, ny));
					}
				}
			}
		}
	}


	int size = 0; // 바이러스 퍼치고 최댓값 저장
	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			if (calcmap[j][i] == 0)
			{
				size++;
			}
		}
	}
	if (size > answer)
	{
		answer = size; // 최댓값을 결과로 초기화
	}
}


void wall(int w, int h, int cnt) // 벽 세우기
{
	if (cnt == 3)
	{
		spread(w, h); // 벽이 3개가 다 세워졌으면 바이러스 퍼치기
		return;
	}
	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{ // 전체 좌표에 접근하며
			if (map[j][i] == 0 && wallmap[j][i] == 0)
			{ // 맵이 빈칸일 때만 벽을 세운다(바이러스 및 벽에는 벽 세울 수 없음)
				wallmap[j][i] = -1; // 벽 세우기
				wall(w, h, cnt + 1); // 벽 세우기(재귀)
				wallmap[j][i] = 0; // 앞의 재귀문을 빠져나오면 현재 위치를 다시 0으로 만들어야 한다.(함수의 재귀적 구성)
			}
		}
	}
}

int main()
{
	int h, w;
	scanf("%d %d", &h, &w);

	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			scanf("%d", &map[j][i]);
			if (map[j][i] == 1)
			{
				check[j][i] = -1;
			}
			if (map[j][i] == 2)
			{
				virus.push_back(make_pair(i, j));
			}
		}
	}

	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			if (map[j][i] == 0)
			{ // 전체 좌표에서 첫 번째 벽을 세우는 과정
				init(w, h); // ccheck에 check 맵 복사(원본 맵 보관해야함)
				wallmap[j][i] = -1; //현재 위치에 벽 세우고
				wall(w, h, 1); // 벽 세우기 재귀함수 입장
				wallmap[j][i] = 0; // 현재 위치 벽 세운거 제거
			}
		}
	}

	printf("%d\n", answer);

	//system("pause");
	return 0;
}
  • 비효율적으로 짠듯 함..

[14889] 스타트와 링크

  • 난이도: 하
    • 문제를 보고 풀이가 바로 떠올랐다.
    • 앞과 동일하게 모든 경우의 수를 비교해봐야 한다.
    • 재귀적 구성 말고 C++ algorithm stl의 next_permutation을 이용해 해결
    • 문제 해결방법 떠올리는것은 문제를 보고 바로, 구현에는 1시간정도 걸렸다.
      • 전체 푸는데 1시간이 걸리지 않았다.
  • 문제
    • https://www.acmicpc.net/problem/14889
    • 2의 배수인 팀원들에 대한 각 상대 능력치가 배열 형태로 주어진다.
    • i와 j가 같은 팀원인 경우, 팀의 능력치는 $S_{ij}+S_{ji}$ 가 된다.
      • 같은 팀이라도 모든 경우의 수를 고려해 팀의 전체 능력치를 계산해야 함
    • 모든 경우의 수를 비교해 팀의 능력치가 최소가 될 때의 차이 최솟값을 출력함
  • 풀이
    • 문제를 팀 가르기, 팀 내 점수 계산, 팀 사이 점수 계산해 최솟값 저장 으로 나눴다.
    • 팀 가르기
      • 전체 팀원에서 절반 길이까지 해당하는 0, 0, …, 1, 1의 선택 수열을 생성한다.(전체 인원의 절반이 선택되도록)
      • do-while, next_permutation으로 수열을 전체 참조하며 모든 경우의수를 판단해 팀을 가른다.
    • 팀 내 점수 계산
      • 갈라진 팀 내 점수를 계산한다.
      • 동일하게 do-while과 next_permutation을 이용해 선택 수열을 생성한다.(2개만 선택되도록)
      • 선택된 두 인원 서로에 대한 능력치를 전체 합에 더하고 리턴
    • 팀 사이 점수 최솟값 계산
      • 위 과정에서 리턴된 값을 토대로 차이를 계산하고, 최솟값을 저장
#pragma warning(disable:4996)
#include <iostream>
#include <queue>
#include <tuple>
#include <cmath>
#include <algorithm>

using namespace std;

int nodes[20][20];
int answer = 2147483647; // int 최댓값 저장용

int calc(vector<int> team) // 팀 내의 능력치 합을 계산해 리턴한다.
{
	int len = team.size();
	vector<int> select(len); // 팀 인원 중 2명만 선택되는 선택수열을 생성
	select[len - 1] = 1;
	select[len - 2] = 1;
	int sum = 0; // 능력치 합 저장
	vector<int> temp; // 선택된 선수 인덱스 저장용

	do
	{
		temp.clear(); // 선택된 선수 인덱스 저장 스택 초기화

		for (int i = 0; i < len; i++)
		{
			if (select[i] == 1)
			{
				temp.push_back(i); // temp에 총 두명을 선택해 인덱스 저장
			}
		}
		sum += nodes[team[temp[0]]][team[temp[1]]]; // temp에는 해당 선수의 인덱스가 저장
		sum += nodes[team[temp[1]]][team[temp[0]]]; // 따라서 team의 인덱스로 들어가 서로 능력치 계산되어 더해져야 함
	} while (next_permutation(select.begin(), select.end())); // 다음 선택수열

	return sum; // 최종 합을 리턴
}

int main()
{
	int N;
	scanf("%d", &N);
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			scanf("%d", &nodes[j][i]);
		}
	}
	
	vector<int> check(N); // 사람들 중 선수 고르기용 선택 수열 생성
	for (int i = 0; i < N / 2; i++)
	{
		check[N - 1 - i] = 1; // 맨 뒤부터 중간까지 1로 채워 선택한다.
	}
	
	int sa;
	int sb;2
	int sub;
	vector<int> idx_a; // a 팀 인덱스
	vector<int> idx_b; // b 팀 인덱스
	do 
	{
		idx_a.clear(); // 각 인덱스 초기화
		idx_b.clear();

		// 선택수열을 이용해 팀을 가름
		for (int i = 0; i < N; i++)
		{
			if (check[i] == 1)
			{
				idx_a.push_back(i);
			}
			else
			{
				idx_b.push_back(i);
			}
		}

		sa = calc(idx_a); // 각 갈라진 팀의 능력치 합을 리턴
		sb = calc(idx_b);
		sub = abs(sa - sb);
		if (sub < answer) // 최솟값을 정답으로 저장
		{
			answer = sub;
		}

	} while (next_permutation(check.begin(), check.end())); // 다음 선택순열

	printf("%d\n", answer);
	//system("pause");
	return 0;
}
  • next_permutation은 좀 느린듯 하다.

191004 백준 알고리즘 문제풀기

|

[17144] 미세먼지 안녕!

  • 난이도: 중
    • 풀이는 금방 떠올랐으나, 실수로 인해 구현에 시간이 오래걸렸다.
    • 완전 탐색이 필요한 경우 큐를 사용하면 되지만, 이 문제는 초 단위로 먼지의 이동과 공기청정기의 바람부는 연산으로 1초 연산으로 나뉜다.
    • 따라서 BFS를 사용하면 더 복잡할 것 같아 일반 for문으로 문제를 풀었다.
  • 문제
    • https://www.acmicpc.net/problem/17144
    • 가로, 세로 길이 각각 c, r인 맵과 시간 t, 그리고 해당 맵에 입력으로 주어졌을 때, t초 후 공간에 존재하는 먼지들의 합을 구한다.
    • 1초 단위로 구동되며 1초동안 아래의 연산이 수행됨
      • 미세먼지의 확산(모든 칸에서 동시에)
        • (r, c)에 있는 미세먼지는 상하좌우로 확산
        • 상하좌우에 공기청정기가 있거나, 칸이 없으면 확산 발생하지 않음
        • 만약 칸에 미세먼지가 5보다 작게 존재할 경우, 미세먼지는 줄어들지 않는다!
        • 확산되는 양은 해당 칸의 먼지/5 이며, 소수점은 버림
        • 해당 칸에 남은 미세먼지 양은 사라진 양 만큼임
          • 해당 칸 처음 먼지량 - 해당 칸 먼지/5 * 퍼진 칸 수
      • 공기청정기의 작동
        • 바람이 나와 위로는 반시계, 아래로는 시계방향으로 바람이 돈다.
        • 바람 불면 미세먼지가 한 칸씩 이동
        • 공기청정기의 바람은 먼지가 하나도 없음(들어간 먼지는 모두 정화)
  • 입력
    • 첫째 줄에 R, C, T (6 ≤ R, C ≤ 50, 1 ≤ T ≤ 1,000) 가 주어진다.
    • 둘째 줄부터 R개의 줄에 Ar,c (-1 ≤ Ar,c ≤ 1,000)가 주어진다. 공기청정기가 설치된 곳은 Ar,c가 -1이고, 나머지 값은 미세먼지의 양이다. -1은 2번 위아래로 붙어져 있고, 가장 윗 행, 아랫 행과 두 칸이상 떨어져 있다.
  • 풀이
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>

using namespace std;

int map[50][50]; // 현재 상황용 맵
int check[50][50]; // 먼지가 퍼지고 난 후의 맵

int dx[] = { -1,1,0,0 }; // 이동 방향 정의
int dy[] = { 0,0,-1,1 };

void spread(int w, int h) // 먼지 퍼치기
{
	for (int y = 0; y < h; y++)
	{
		for (int x = 0; x < w; x++)
		{

			if (check[y][x] > 4)
			{ // 전체 맵에 대해 해당 칸에 먼지가 4 초과 존재 할 경우
				int cx = x; // 현재 칸 정의
				int cy = y;
				int cnt = 0; // 퍼진 양 계산용 카운팅
				int d = map[cy][cx] / 5; // 퍼지는 미세먼지 양(먼지 퍼지기 전에서 계산)

				for (int i = 0; i < 4; i++)
				{
					int nx = cx + dx[i]; // 다음 칸 정의
					int ny = cy + dy[i];

					if (0 <= nx && nx < w && 0 <= ny && ny < h && check[ny][nx] != -1) // 다음 칸이 범위 안이고, 공기청정기가 아닌 경우
					{
						check[ny][nx] += d; // 먼지가 퍼진다.
						cnt++; // 퍼진 양 카운팅
					}
				}
				int nd = map[cy][cx] / 5 * cnt; // 퍼진 먼지 양 계산
				check[cy][cx] -= nd; // 현재 칸에 퍼진 먼지 양 만큼 빼줌
			}
		}
	}
}

void wind(int ay, int w, int h) // 바람 
{
	int uy = ay - 1;
	int ly = ay;

	int br, tr, tl, bl;

	// 윗부분 움직이기
	br = check[uy][w - 1];
	tr = check[0][w - 1];
	tl = check[0][0];
	bl = check[uy][0];
	
	//방향 1: y = uy
	for (int i = w - 1; i > 0; i--)
	{
		check[uy][i] = check[uy][i - 1];
	}
	check[uy][0] = -1;
	check[uy][1] = 0;

	//방향 2: x=7
	for (int j = 0; j < uy; j++)
	{
		if (j + 1 == uy)
		{
			check[j][w - 1] = br;
		}
		else
		{
			check[j][w - 1] = check[j + 1][w - 1];
		}
	}
	//방향 3: y=0
	for (int i = 0; i < w - 1; i++)
	{
		if (i + 1 == w - 1)
		{
			check[0][i] = tr;
		}
		else
		{
			check[0][i] = check[0][i + 1];
		}
	}
	
	//방향 4: x=0
	for (int j = uy; j > 0; j--)
	{
		if (j - 1 == 0)
		{
			check[j][0] = tl;
		}
		else
		{
			check[j][0] = check[j - 1][0];
		}
	}
	check[uy][0] = -1;

	// 아랫부분 움직이기
	br = check[h - 1][w - 1];
	tr = check[ly][w - 1];
	tl = check[ly][0];
	bl = check[h - 1][0];

	//방향 1: y=ly
	for (int i = w - 1; i > 0; i--)
	{
		check[ly][i] = check[ly][i - 1];
	}
	check[ly][0] = -1;
	check[ly][1] = 0;

	//방향 2: x=w-1
	for (int j = h - 1; j > ly; j--)
	{
		if (j - 1 == ly)
		{
			check[j][w - 1] = tr;
		}
		else
		{
			check[j][w - 1] = check[j - 1][w - 1];
		}
	}

	//방향 3: y=h-1
	for (int i = 0; i < w - 1; i++)
	{
		if (i + 1 == w - 1)
		{
			check[h - 1][i] = br;
		}
		else
		{
			check[h - 1][i] = check[h - 1][i + 1];
		}
	}

	//방향 4: x=0
	for (int j = ly; j < h - 1; j++)
	{
		if (j + 1 == h - 1)
		{
			check[j][0] = bl;
		}
		else
		{
			check[j][0] = check[j + 1][0];
		}
	}
	check[ly][0] = -1;
}

int summation(int w, int h) // 합 계산용
{
	int result = 2; // 공기청정기 2칸이 각각 -1이므로 2로 초기화
	for (int j = 0; j < h; j++)
	{
		for (int i = 0; i < w; i++)
		{
			result += check[j][i];
		}
	}
	return result;
}

int main()
{
	int r, c, t; // r: 세로길이, c: 가로길이, t: test case
	scanf("%d %d %d", &r, &c, &t);

	int ax, ay;

	for (int j = 0; j < r; j++)
	{
		for (int i = 0; i < c; i++)
		{
			scanf("%d", &map[j][i]);
			if (map[j][i] == -1)
			{
				ax = i;
				ay = j;
			}
			if (map[j][i] != 0)
			{
				check[j][i] = map[j][i];
			}
		}
	}

	while (t--)
	{
		spread(c, r);
		wind(ay, c, r);

		for (int j = 0; j < r; j++)
		{
			for (int i = 0; i < c; i++)
			{
				map[j][i] = check[j][i]; // 먼지 퍼진 맵을 현재 맵으로 초기화한다.(그래야 다음 t때 퍼지는 먼지  사용)
			}
		}
	}

	int answer = summation(c, r);
	printf("%d\n", answer);

	//system("pause");
	return 0;
}

[14503] 로봇 청소기

  • 난이도: 상
    • 풀이를 떠올렸지만 조건이 많아 구현하는데 애먹었다.
    • 특히 짜증났던게, 조건이 명확하지 않고 애매하게 표현된다.
    • 왼쪽으로 뱅글뱅글 돌아가며 찾아야 한다.
  • 문제
    • https://www.acmicpc.net/problem/14503
      1. 현재 위치를 청소한다
      2. 현재 위치에서 현재 방향 기준으로 왼쪽부터 탐색한다.
      • 현재 위치에서 왼쪽이 청소되지 않았다면, 그 방향으로 회전 후 이동 후 청소한다(1번)
      • 왼쪽이 청소되어있거나 벽이라면, 해당 방향으로 회전만 한다. 그리고 다시 2번으로 돌아간다.
      • 네 방향 모두 청소가 되어있거나 벽이라면, 우선 바라보는 방향을 유지한 채 한 칸 후진 후 2번으로 돌아간다.
      • 네 방향 모두 청소가 되어있거나 벽이고, 후진 불가능하다면 작동을 멈춘다.
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <tuple>
#include <queue>

using namespace std;

int map[50][50];
int check[50][50];
int answer = 0;

int dx[] = { 0,1,0,-1 }; // 순서대로 청소기의 방향을 0,1,2,3으로 정함
int dy[] = { -1,0,1,0 };

int left(int n)
{ // 좌회전
	if (n == 3) return 2;
	if (n == 2) return 1;
	if (n == 1) return 0;
	else return 3;
}
int right(int n)
{ // 우회전
	if (n == 3) return 0;
	if (n == 2) return 3;
	if (n == 1) return 2;
	else return 1;
}
int back(int n)
{ // 뒤돌기
	if (n == 3) return 1;
	if (n == 2) return 0;
	if (n == 1) return 3;
	else return 2;
}

void clean(int x, int y, int d, int w, int h)
{ // 청소
	// 변수선언 및 현재위치 초기화
  int cx, cy, cd;
	int nx, ny, nd;
	cx = x;
	cy = y;
	cd = d;

	while (1)
	{
		if (map[cy][cx] == 0 && check[cy][cx] == 0)
		{ // 현재 위치에 벽이 없고 청소되어있지 않다면 청소한다.
			answer++;
			check[cy][cx] = answer;
		}

		//왼쪽 탐색
		nd = left(cd); // 현재 방향에서 왼쪽으로 돌고 다음 방향을 정의
		nx = cx + dx[nd];
		ny = cy + dy[nd];

		if (map[ny][nx] == 0 && check[ny][nx] == 0)
    { // 만약 다음 방향이 벽이 아니고 청소하지 않았다면
			cd = nd;	//회전한 방향을 현재 방향으로 초기화 하고 continue
			cy = ny;
			cx = nx;
			continue;
		}
		else
		{ // 다음 방향이 청소 불가능하다면
			int turn_cnt = 0;
			while (check[ny][nx] != 0)
			{ // 다음 청소할곳 찾을때까지 왼쪽으로 회전
				nd = left(nd);
				nx = cx + dx[nd];
				ny = cy + dy[nd];
				turn_cnt++;
				if (turn_cnt == 4) break; // 한 바퀴 돌았는데도 못찾았으면 빠져 나온 후 if문 이동
			}
			if (turn_cnt == 4)
			{ // 네 방향 모두 청소가 이미 되어있거나 벽이라면
				nd = back(cd); //후진방향
				nx = cx + dx[nd]; // 후진 좌표 설정
				ny = cy + dy[nd];
				if (map[ny][nx] == 1)
				{ // 만약 후진 방향이 벽이라면
					break; // 종료
				}
				cx = nx; // 현재 위치를 후진 위치로 초기화
				cy = ny;
        //cd는 원래 방향이 그대로 유지되어야 하므로 별도 초기화가 필요 없음
				continue;
			}
			else
      { // 만약 한 바퀴 돌기 전에 다음 청소할 곳 찾았으면
        cd = right(nd); // while문 시작부분 check 후 왼쪽 탐색 조건을 만족시키기 위해 오른쪽을 다시 바라보게 해야 한다.
      }
		}
	}
}

int main()
{
	int N, M; // N: H, M: W
	scanf("%d %d", &N, &M);
	int r, c, d; // r: y, c: x
	scanf("%d %d %d", &r, &c, &d);
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < M; i++)
		{
			scanf("%d", &map[j][i]);
			if (map[j][i] == 1)
			{
				check[j][i] = -1;
			}
		}
	}

	clean(c, r, d, M, N);

	printf("%d\n", answer);

	//system("pause");
	return 0;
}

Putty, WinSCP access denied 문제 해결

|

Putty, WinSCP access denied 문제 해결

  • 이전엔 없었지만, 최근 보안 키 값이 변경되었다는 경고문구가 뜨면서 우분투 서버 putty, winscp 접속이 불가능한 현상 발생
    • 계정 및 비밀번호를 올바르게 입력해도 계속 access denied출력 되는 현상
  • sshd_config 파일 수정으로 해결 가능함

  • 경로: /etc/ssh/sshd_config
    • $ sudo gedit /etc/ssh/sshd_config 입력
  • 입력 후, 내용에서 아래 값을 찾아 변경
    • 기존: PermitRootLogin prohibit-password
    • 변경: PermitRootLogin yes
  • 전체적인 config 파일 내용은 아래와 같음


# Package generated configuration file
# See the sshd_config(5) manpage for details

# What ports, IPs and protocols we listen for
Port 22
# Use these options to restrict which interfaces/protocols sshd will bind to
#ListenAddress ::
#ListenAddress 0.0.0.0
Protocol 2
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
#Privilege Separation is turned on for security
UsePrivilegeSeparation yes

# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 1024

# Logging
SyslogFacility AUTH
LogLevel INFO

# Authentication:
LoginGraceTime 120
#PermitRootLogin prohibit-password
PermitRootLogin yes
StrictModes yes

RSAAuthentication yes
PubkeyAuthentication yes
#AuthorizedKeysFile	%h/.ssh/authorized_keys

# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes

# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no

# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no

# Change to no to disable tunnelled clear text passwords
#PasswordAuthentication yes

# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes

# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes

X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
#UseLogin no

#MaxStartups 10:30:60
#Banner /etc/issue.net

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

Subsystem sftp /usr/lib/openssh/sftp-server

# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication.  Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes


191003 백준 알고리즘 문제풀기

|

[13460] 구슬 탈출 2

  • 난이도: 극악
    • 풀이 떠올리는 법은 쉽게 떠올라서 금방 풀을 줄 알았으나, 결국 두 시간 이상 고민하고 풀지 못함
    • 다른사람 코드를 참고하여 풀음
    • 자괴감 들고 괴로웠던 문제!
  • 문제
    • 세로 N, 가로 M 길이의 맵에서 모서리 부분은 모드 #으로 표시된 벽
      • N, M은 각각 3 이상 10 이하
    • 그 맵에서 빨간 구슬 R과 파란 구슬 B, 구멍 O, 가로막힌 벽 #, 통과할 수 있는 부분 . 이 주어짐
    • 조건
      • 기울이는 동작은 상, 하, 좌, 우로 할 수 있으며, 가로막힌 벽이 있을때까지 계속 그 방향으로 전진
      • 빨간 구슬과 파란 구슬은 동시에 움직인다.
      • 각 구슬은 한 칸을 모두 차지한다.
        • 빨간 구슬과 파란 구슬은 겹칠 수 없음
      • 기울이는 동작은 구슬이 더 이상 움직이지 않을 때까지 수행
    • 정답
      • 보드의 상태가 주어졌을 때, 최소 몇 번만에 빨간 구슬을 구멍을 통해 빼낼 수 있는지 구하기
    • 성공 조건
      • 빨간 구슬만 구멍을 통해 빼내는 것이다.
    • 실패 조건
      • 파란 구슬이 구멍에 들어가거나, 10번 이상 걸려서 빼내면 실패한다.
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>

using namespace std;

char map[10][10];
int check[10][10][10][10]; // ry, rx, by, bx

int dx[] = { -1, 1, 0, 0 }; // 움직이는 방향
int dy[] = { 0, 0, -1, 1 };

struct ball
{
	int rx, ry, bx, by; // 빨간 구슬과 파란 구슬의 좌표 구조체
};

void bfs(int w, int h, int rx, int ry, int bx, int by)
{
	queue<ball> q;
	check[ry][rx][by][bx] = 1; // 시작 위치를 방문으로 check
	q.push({ rx,ry,bx,by }); // 큐에 넣음

	int cnt = 0; // 방향 회전한 횟수 카운팅

	while (!q.empty())
	{
		int len = q.size();
		while (len--)
		{ // 현재 큐
			int crx, cry, cbx, cby;
			crx = q.front().rx; // 현재 위치를 초기화
			cry = q.front().ry;
			cbx = q.front().bx;
			cby = q.front().by;
			q.pop();
			
			int mrx, mry, mbx, mby; // 중간 계산용 위치

			if (map[cry][crx] == 'O' && map[cby][cbx] != 'O')
			{ // 현재 빨간구슬만 구멍이면
				printf("%d\n", cnt); // 결과 출력후 종료
				return;
			}

			for (int i = 0; i < 4; i++)
			{ // 다음 위치 설정
				int nrx, nry, nbx, nby;

				mrx = crx;
				mry = cry;
				mbx = cbx;
				mby = cby;

				//빨간구슬 이동(벽 끝까지)
				while (true)
				{
					nrx = mrx + dx[i];
					nry = mry + dy[i];
					if (map[nry][nrx] == '#' || map[mry][mrx] == 'O')
						break;
					mrx = nrx;
					mry = nry;
				}

				//파란구슬 이동(벽 끝까지)
				while (true)
				{
					nbx = mbx + dx[i];
					nby = mby + dy[i];
					if (map[nby][nbx] == '#' || map[mby][mbx] == 'O')
						break;
					mbx = nbx;
					mby = nby;
				}

				if (mrx == mbx && mry == mby)
				{
					if (map[mby][mbx] == 'O')
						continue;
					if (abs(crx - mrx) + abs(cry - mry) > abs(cbx - mbx) + abs(cby - mby))
					{ // 구슬 위치가 겹치는 경우, 먼저 왔느냐에 나중에 온 구슬을 한칸 전으로 미룸
						mrx -= dx[i];
						mry -= dy[i];
					}
					else
					{
						mbx -= dx[i];
						mby -= dy[i];
					}
				}
				if (check[mry][mrx][mby][mbx] == 0)
				{ // 만들어진 위치 check
					check[mry][mrx][mby][mbx] = 1;
					q.push({ mrx,mry,mbx,mby });
				}
			}
		}
		if (cnt == 10)
		{
			printf("-1\n");
			return;
		}
		cnt++;
	}
	printf("-1\n");
	return;

}

int main()
{
	int n, m; // n: 세로, m: 가로
	scanf("%d %d", &n, &m);

	int hx, hy, rx, ry, bx, by;

	for (int j = 0; j < n; j++)
	{
		for (int i = 0; i < m; i++)
		{
			cin >> map[j][i];
			if (map[j][i] == 'O')
			{
				hx = i;
				hy = j;
			}
			if (map[j][i] == 'R')
			{
				rx = i;
				ry = j;
			}
			if (map[j][i] == 'B')
			{
				bx = i;
				by = j;
			}
		}
	}

	bfs(m, n, rx, ry, bx, by);

	//system("pause");
	return 0;
}

[14891] 톱니바퀴

  • https://www.acmicpc.net/problem/14891
  • 난이도: 하
    • 문제를 보고 풀이 방법이 바로 떠올랐다.
    • 전체 푸는데 1시간이 걸리지 않음
  • 문제
    • 전체 4개의 톱니가 있고, 각 톱니에는 8개의 이빨이 있음
    • 각 톱니에는 N(0)과 S(1) 극성이 있으며, 맞 닿아 있는 톱니 이빨의 극성이 서로 달라야 맞물려 돌아가고, 갖으면 돌지 않음
    • 톱니이기 때문에 옆 톱니와는 반대 방향으로 돌음
    • 입력으로 톱니의 상태, 톱니 돌릴 횟수, 톱니 번호, 방향이 순서대로 입력됨
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>

using namespace std;

int t[4][8]; // 톱니 상태 저장용

void left(int arr[])
{ // 해당 톱니 왼쪽으로 회전시키기
	int temp = arr[0];
	for (int i = 0; i < 7; i++)
	{
		arr[i] = arr[i + 1];
	}
	arr[7] = temp;
}

void right(int arr[])
{ // 해당 톱니 오른쪽으로 회전시키기
	int temp = arr[7];
	for (int i = 7; i >= 0; i--)
	{
		arr[i] = arr[i - 1];
	}
	arr[0] = temp;
}

void rotate(int tn, int dir)
{
	int states[] = { 0,0,0,0 }; // 톱니를 회전할지, 방향 어느방향인지의 상태를 담는다.
	states[tn - 1] = dir; // 현재 톱니의 상태는 현재 방향의 상태로 초기화

	int cur = dir; // 현재 톱니의 방향을 정하고
	for (int i = tn - 1; i > 0; i--) // 현재 톱니(tn)기준 왼쪽 톱니들의 회전조건 검사
	{
		if (t[i][6] != t[i - 1][2]) // 만약 맞물려 있는 톱니 이빨의 극성이 서로 다르다면
		{
			cur = 0 - cur; // 현재 톱니와 반대로 방향을 정하고
			states[i - 1] = cur; // 그 방향으로 상태를 설정
		}
		else break; // 만약 맞물려 있지 않다면 그 왼쪽 톱니들은 더 볼 필요가 없다.
	}

	cur = dir; // 다시 현재 톱니 상태를 초기화하고
	for (int i = tn - 1; i < 4 - 1; i++) // 현재 톱니(tn)기준 오른쪽 톱니들의 회전조건 검사
	{
		if (t[i][2] != t[i + 1][6]) // 맞물려 있는 톱니 이빨의 극성이 서로 다르다면
		{
			cur = 0 - cur; // 반대 방향으로 설정 후
			states[i + 1] = cur; // 해당 톱니 상태를 초기화
		}
		else break; // 중간에 끊겨있다면 더 살펴보지 않아야 하므로 break
	}

	for (int i = 0; i < 4; i++)
	{ // 전체 state에 따라 왼쪽, 오른쪽으로 각각 회전시킴
		if (states[i] == -1)
		{
			left(t[i]);
		}
		else if (states[i] == 1)
		{
			right(t[i]);
		}
	}
}

int main()
{
	for (int j = 0; j < 4; j++)
	{
		for (int i = 0; i < 8; i++)
		{
			scanf("%1d", &t[j][i]);
		}
	}

	int k;
	scanf("%d", &k);
	int tn, dir;
	while (k--)
	{ // 톱니 번호와 회전 방향을 입력받고
		scanf("%d %d", &tn, &dir);
		
		rotate(tn, dir); // 회전시킨다.
	}
  // 문제의 조건에 맞게 답을 출력함
	int answer = t[0][0] * 1 + t[1][0] * 2 + t[2][0] * 4 + t[3][0] * 8;

	printf("%d\n", answer);

	//system("pause");
	return 0;
}