191005 백준 알고리즘 문제풀기
05 Oct 2019 | baekjoon[14502번] 연구소
- 난이도: 중상
- 바이러스가 퍼지는것까지는 bfs로 쉽게 구현하는 줄 알았는데, 벽을 랜덤하게 세우는데서 애를 먹었다.
- 함수의 재귀적 구성을 통해 벽을 세움
- 또한, 계산과정에서 계속 올바르게 벽이 세워져도 bfs에서 제대로 동작하지 않는 경우가 생겼다
- 이는 연산용 별도의 map을 할당해서 해결했음
- 전체 푸는데 2시간정도 소요된듯 하다..
- 문제풀이 떠올리는데 30분도 안걸렸지만, 구현 및 디버깅에서 엄청나게 소모됨
- 바이러스가 퍼지는것까지는 bfs로 쉽게 구현하는 줄 알았는데, 벽을 랜덤하게 세우는데서 애를 먹었다.
- 문제
- 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은 좀 느린듯 하다.