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은 좀 느린듯 하다.