191014 백준 알고리즘 문제풀기

|

[17142] 연구소 3

  • 난이도: 상
  • 문제 풀이 떠올리는데 30분, 구현까지 1시간 걸렸지만 디버깅만 주구장창 하다가 결국 틀렸다.
  • 시간 초과
  • 너무 조건이 많아 어렵다
  • 특히, 바이러스를 퍼치는 부분이 정말 까다로웠다.
  • 제일 중요한것은 문제를 정확하게 이해하고 풀기 시작하는 것 같다.
  • 정확하기 이해하기 전부터 풀기 시작하다보니 결국 산으로 간다…

  • 문제
    • https://www.acmicpc.net/problem/17142
    • NxN 크기 맵이 주어진다.
    • 맵에서 바이러스들이 2, 벽이 1, 공간이 0으로 주어지며, 임의의 바이러스 M개를 골라 전체 맵에 퍼칠 경우 걸리는 최소시간을 출력.
    • 맵에는 바이러스들이 활성/비활성 상태로 존재하며, M개의 바이러스만 골라 활성화 상태로 바꾼다.
    • 활성화 상태가 된 바이러스는 상, 하, 좌, 우로 1초에 1칸씩 퍼져나간다.
    • 활성 바이러스가 비활성 바이러스가 있는 칸으로 가면 비활성 바이러스가 활성으로 변한다
      • 표현이 조금 애매해서 이해하는데 한참 걸렸다.
      • 그러니까, 바이러스가 퍼지는 칸에는 비활성 바이러스가 있건 없건 상관이 없다.
      • 모든 칸에 퍼지는 시간을 구하는것이 문제였다. 따라서 비활성 바이러스도 바이러스로 쳐야 한다.
        • 문제 자체를 __모든 칸에 활성 바이러스를 퍼치는데 걸리는 시간__으로 이해해서 한참 헤멧다.
        • 비활성 바이러스는 활성화 되지 않아도 바이러스다..
  • 풀이
    • 문제를 나누면
      • 상태 맵을 입력 받는다.
      • 활성화 시킬 바이러스 3개를 고르고
      • BFS를 이용해 바이러스를 퍼친다.
        • 다만, 1초에 1칸씩 퍼지므로 이에 유의해서 BFS를 정의해야 한다.
#pragma warning(disable:4996)
#include <iostream>
#include <cstdio>
#include <queue>

#define MAX 2147483647

using namespace std;

int map[51][51];
int check[51][51];
int calcmap[51][51];

int dx[] = { -1,1,0,0 };
int dy[] = { 0,0,-1,1 };

int answer = MAX;

int emptycnt = 0;

void init_calcmap(int N, queue<pair<int,int>> &virus)
{
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			calcmap[j][i] = check[j][i];
			if (calcmap[j][i] == -2)
			{//만약 바이러스 위치라면
				calcmap[j][i] = 1;
				virus.push(make_pair(i, j));
			}
			if (calcmap[j][i] != 1 && map[j][i] == 2)
			{//만약 비활성화 바이러스라면 -3으로 초기화
				calcmap[j][i] = -3;
			}
		}
	}
}

void spread_virus(int N)
{
	queue<pair<int, int>> virus;
	
	init_calcmap(N, virus);
	
	//int infect = 0;
	//int time = 0;

	while (!virus.empty())
	{
		int cx = virus.front().first;
		int cy = virus.front().second;
		virus.pop();
		for (int i = 0; i < 4; i++)
		{
			int nx = cx + dx[i];
			int ny = cy + dy[i];
			//범위 벗어나면 끝
			if (nx < 0 || N <= nx || ny < 0 || N <= ny) continue;

			if (calcmap[ny][nx] == 0)
			{ // 만약 다음 위치 방문한적 없다면
				//time = calcmap[ny][nx] = calcmap[cy][cx] + 1; // 방문한다.
				calcmap[ny][nx] = calcmap[cy][cx] + 1; // 방문한다.
				virus.push(make_pair(nx, ny));
				//infect++;
			}
			if (calcmap[ny][nx] == -3)
			{ // 만약 다음 위치가 비활성화 바이러스라면
			  //만약 주변에 바이러스가 꽉찻다면 
				int cnt = 0;
				for (int k = 0; k < 4; k++)
				{
					int tx = nx + dx[k];
					int ty = ny + dy[k];
					//범위 안에서
					if (tx < 0 || N <= tx || ty < 0 || N <= ty) continue;
					if (calcmap[ty][tx] == 0) cnt++;
				}
				if (cnt == 0)
				{
					//time = calcmap[ny][nx] = calcmap[cy][cx];// 현재상태로 활성화
					calcmap[ny][nx] = calcmap[cy][cx];// 현재상태로 활성화
					virus.push(make_pair(nx, ny));
					//infect++;
				}
				else
				{
					//time = calcmap[ny][nx] = calcmap[cy][cx] + 1; // 활성화
					calcmap[ny][nx] = calcmap[cy][cx] + 1; // 활성화
					virus.push(make_pair(nx, ny));
					//infect++;
				}
			}

		}
	}
	//printf("time: %d, infect: %d\n", time, infect);
	int maxval = -1;
	bool zero = false;

	//최댓값 찾기
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			if (map[j][i] != 2 && calcmap[j][i] == 0)
			{
				zero = true;
				//break;
				return;
			}
			if (maxval < calcmap[j][i])
			{
				maxval = calcmap[j][i];
			}
		}
	}

	if (!zero && maxval < answer)
	{
		answer = maxval;
	}
}

void virus_choose(int N, int M, int cnt)
{
	if (cnt == M)
	{
		spread_virus(N); //바이러스 퍼치기
		return;
	}
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			if (check[j][i] == 0 && map[j][i] == 2)
			{
				check[j][i] = -2; // 바이러스 선택
				virus_choose(N, M, cnt + 1);
				check[j][i] = 0;
			}
		}
	}
}



int main()
{
	int N, M;
	scanf("%d %d", &N, &M);
	int wall = 0;
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			scanf("%d", &map[j][i]);
			if (map[j][i] == 1)
			{
				check[j][i] = -1;
				wall++;
			}
		}
	}

	//emptycnt = N * N - wall - M;
	//printf("%d\n", emptycnt);

	// 바이러스 고르기
	for (int j = 0; j < N; j++)
	{
		for (int i = 0; i < N; i++)
		{
			if (check[j][i] == 0 && map[j][i] == 2)
			{//바이러스 위치라면
				check[j][i] = -2; // 바이러스 선택
				virus_choose(N, M, 1);
				check[j][i] = 0;
			}
		}
	}

	if (answer == MAX)
	{
		printf("-1\n");
	}
	else
	{
		printf("%d\n", answer - 1);
	}
  
	std::system("pause");
	return 0;
}
  • 퍼 온 간단하게 구현된 코드다…
    • 출처: https://rebas.kr/846 [PROJECT REBAS]
    • 복잡한 알고리즘을 간단하게 구현했다…
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

struct virus {
    int x, y;
};

int a[50][50], dist[50][50];
int n, m, k, ans=1e9;
bool select[10];
vector<virus> v;
queue<virus> q;
const int dx[] = {-1, 0 , 1, 0}, dy[] = {0, 1, 0, -1};

void bfs() {
    int infect=0, times=0;
    while (!q.empty()) {
        int x = q.front().x, y = q.front().y; q.pop(); 
        for (int i=0; i<4; i++) {
            int nx = x+dx[i], ny = y+dy[i];
            if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
            if (a[nx][ny] != 1 && dist[nx][ny] == -1) {
                dist[nx][ny] = dist[x][y]+1;
                if (a[nx][ny] == 0) {
                    infect += 1;
                    times = dist[nx][ny];
                }
                q.push({nx, ny});
            }
        }
    }
    if (infect == k && ans > times) ans = times;
}

void solve(int idx, int cnt, int d) {
    if (cnt == m) {
        memset(dist, -1, sizeof(dist));
        for (int i=0; i<d; i++) {
            if (select[i]) {
                q.push({v[i].x, v[i].y});
                dist[v[i].x][v[i].y] = 0;
            }
        }
        bfs();
        return;
    }
    for (int i=idx; i<d; i++) {
        if (!select[i]) {
            select[i] = true;
            solve(i+1, cnt+1, d);
            select[i] = false;
        }
    }
}

int main() {
    scanf("%d %d", &n, &m);
    for (int i=0; i<n; i++) {
        for (int j=0; j<n; j++) {
            scanf("%d", &a[i][j]);
            if (a[i][j] == 2) v.push_back({i, j});
            if (a[i][j] == 0) k += 1;
        }
    }
    solve(0, 0, v.size());
    printf("%d\n", ans == 1e9 ? -1 : ans);
    return 0;
}