https://www.acmicpc.net/problem/16946
그냥 bfs, dfs 문제이겠거니 해서 풀었는데 시간초과가 떠서 당황했다.
처음 풀이
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
static int N,M;
static int[][] map;
static int[][] resultMap;
static boolean [][] visited;
static int[][] nextDir = {{0,1},{0,-1},{1,0},{-1,0}};
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
map = new int[N][M];
resultMap = new int[N][M];
for(int i=0;i<N;i++) {
String line = br.readLine();
for(int j=0;j<M;j++) {
map[i][j] = line.charAt(j)-'0';
}
}
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
resultMap[i][j]=getResult(i,j);
}
}
StringBuffer sb = new StringBuffer();
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
sb.append(resultMap[i][j]);
}
sb.append("\n");
}
System.out.println(sb);
}
public static int getResult(int n,int m) {
if(map[n][m]==0) return 0;
visited = new boolean[N][M];
map[n][m]=0;
Queue <int[]> q = new ArrayDeque<>();
q.add(new int[] {n,m});
int visitCount = 0;
while(!q.isEmpty()) {
int [] now = q.poll();
int nowI = now[0];
int nowJ = now[1];
if(visited[nowI][nowJ]) continue;
visited[nowI][nowJ] = true;
visitCount++;
for(int k=0;k<4;k++) {
int nextI = nowI + nextDir[k][0];
int nextJ = nowJ + nextDir[k][1];
if(nextI < 0 || nextI >=N || nextJ <0 || nextJ >=M) continue;
if(visited[nextI][nextJ]) continue;
if(map[nextI][nextJ]==0) {
q.add(new int[] {nextI,nextJ});
}
}
}
map[n][m]=1;
return visitCount%10;
}
}
getResult는 벽인경우 벽을 허물고 주변 빈공간을 탐색한 뒤 다시 벽을 복구하도록 했다.
뭔가 줄이는 방법이 있겠지 해서 여러번 시도해봤는데, 한 2시간 생각하다가 벽기준이 아니라 빈칸 기준으로 생각하면 시간을 줄일 수 있다는 것을 알게되었다.
벽1 | 빈칸그룹1 | 벽2 |
빈칸그룹2 | 벽3 | 빈칸그룹3 |
벽4 | 빈칸그룹4 | 벽5 |
벽1 = 1 + 빈칸그룹1 + 빈칸그룹2. 벽2 = 1 + 빈칸그룹1 + 빈칸그룹3 ....
기존 방식대로라면 벽마다 빈칸그룹을 다 탐색하는데, 먼저 빈칸그룹을 탐색해서 몇개나 갈 수 있는지 체크하면 연산을 줄일 수 있다.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
static int N,M;
static int[][] map;
static int[][] resultMap;
static int[][] group;
static int[][] nextDir = {{0,1},{0,-1},{1,0},{-1,0}};
static int groupNum;
static List<Integer> groupMemberCount;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
map = new int[N][M];
resultMap = new int[N][M];
group = new int[N][M];
groupMemberCount = new ArrayList<>();
groupMemberCount.add(0);
for(int i=0;i<N;i++) {
String line = br.readLine();
for(int j=0;j<M;j++) {
map[i][j] = line.charAt(j)-'0';
}
}
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
if(map[i][j]==1) continue; //벽
if(group[i][j] != 0) continue; // 이미 그룹이 있음
groupMemberCount.add(getGroup(i,j));
}
}
StringBuffer sb = new StringBuffer();
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
sb.append(getResult(i,j));
}
sb.append("\n");
}
System.out.println(sb);
}
public static int getGroup(int n,int m) {
Queue <int[]> q = new ArrayDeque<>();
q.add(new int[] {n,m});
int visitCount = 0; //그룹에서 이동할수 있는칸 수 카운트
groupNum++;
while(!q.isEmpty()) {
int [] now = q.poll();
int nowI = now[0];
int nowJ = now[1];
if(group[nowI][nowJ]!=0) continue; // 이미 그룹이 있음
group[nowI][nowJ] = groupNum;
visitCount++;
for(int k=0;k<4;k++) {
int nextI = nowI + nextDir[k][0];
int nextJ = nowJ + nextDir[k][1];
if(nextI < 0 || nextI >=N || nextJ <0 || nextJ >=M) continue;
if(group[nextI][nextJ]!=0) continue; // 탐색할 칸이 이미 그룹이 있음
if(map[nextI][nextJ]==0) { // 그룹없는 빈칸
q.add(new int[] {nextI,nextJ});
}
}
}
return visitCount;
}
public static int getResult(int i,int j) {
if(map[i][j]==0) return 0;
int visitCount = 1; // 벽을 부수므로 1
for(int k=0;k<4;k++) { // 사방 4칸의 그룹의 갈수있는 칸 수 더하기
int nextI = i + nextDir[k][0];
int nextJ = j + nextDir[k][1];
if(nextI < 0 || nextI >=N || nextJ <0 || nextJ >=M) continue;
if(map[nextI][nextJ]==0) {
visitCount += groupMemberCount.get(group[nextI][nextJ]); // 해당그룹의 칸 수
}
}
return visitCount%10;
}
}
group[][] 에 해당 칸의 그룹번호를 넣게 했고 (0인경우 그룹 없음) 이렇게 하니까 이전버전의 visited 체크도 한번에 할 수 있었다.
groupMemberCount는 0번을 더미로 넣고 해당 그룹번호를 넣으면 몇칸 이동할 수 있는지 뽑을 수 있게 했다.이번엔 맞겠지 해서 돌렸는데 또..
왜틀린지 몰라서 반례를 찾다가
빈칸그룹1 | 벽1 | 빈칸그룹2 |
벽2 | 빈칸그룹2 | 빈칸그룹2 |
빈칸그룹2 | 빈칸그룹2 | 빈칸그룹2 |
상하좌우에 같은 빈칸그룹이 있으면 여러번 연산되는 것을 알게 되었다. ex) 벽1 = 1 + 빈칸그룹1 + 빈칸그룹2 + 빈칸그룹2
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.StringTokenizer;
public class Main {
static int N,M;
static int[][] map;
static int[][] resultMap;
static int[][] group;
static int[][] nextDir = {{0,1},{0,-1},{1,0},{-1,0}};
static int groupNum;
static List<Integer> groupMemberCount;
public static void main(String[] args) throws NumberFormatException, IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
map = new int[N][M];
resultMap = new int[N][M];
group = new int[N][M];
groupMemberCount = new ArrayList<>();
groupMemberCount.add(0);
for(int i=0;i<N;i++) {
String line = br.readLine();
for(int j=0;j<M;j++) {
map[i][j] = line.charAt(j)-'0';
}
}
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
if(map[i][j]==1) continue; //벽
if(group[i][j] != 0) continue; // 이미 그룹이 있음
groupMemberCount.add(getGroup(i,j));
}
}
StringBuffer sb = new StringBuffer();
for(int i=0;i<N;i++) {
for(int j=0;j<M;j++) {
sb.append(getResult(i,j));
}
sb.append("\n");
}
System.out.println(sb);
}
public static int getGroup(int n,int m) {
Queue <int[]> q = new ArrayDeque<>();
q.add(new int[] {n,m});
int visitCount = 0; //그룹에서 이동할수 있는칸 수 카운트
groupNum++;
while(!q.isEmpty()) {
int [] now = q.poll();
int nowI = now[0];
int nowJ = now[1];
if(group[nowI][nowJ]!=0) continue; // 이미 그룹이 있음
group[nowI][nowJ] = groupNum;
visitCount++;
for(int k=0;k<4;k++) {
int nextI = nowI + nextDir[k][0];
int nextJ = nowJ + nextDir[k][1];
if(nextI < 0 || nextI >=N || nextJ <0 || nextJ >=M) continue;
if(group[nextI][nextJ]!=0) continue; // 탐색할 칸이 이미 그룹이 있음
if(map[nextI][nextJ]==0) { // 그룹없는 빈칸
q.add(new int[] {nextI,nextJ});
}
}
}
return visitCount;
}
public static int getResult(int i,int j) {
if(map[i][j]==0) return 0;
HashSet<Integer> visitGroup = new HashSet<>(); // 그룹중복방지용
int visitCount = 1; // 벽을 부수므로 1
for(int k=0;k<4;k++) { // 사방 4칸의 그룹의 갈수있는 칸 수 더하기
int nextI = i + nextDir[k][0];
int nextJ = j + nextDir[k][1];
if(nextI < 0 || nextI >=N || nextJ <0 || nextJ >=M) continue;
if(map[nextI][nextJ]==0 && !visitGroup.contains(group[nextI][nextJ])) {
visitGroup.add(group[nextI][nextJ]); // 방문한 그룹 체크
visitCount += groupMemberCount.get(group[nextI][nextJ]); // 해당그룹의 칸 수
}
}
return visitCount%10;
}
}
Set을 사용해서 방문한 그룹을 체크해서 중복되는 경우를 없앴다.
'개발 > 알고리즘' 카테고리의 다른 글
백준 16234 인구 이동 (1) | 2022.06.14 |
---|---|
백준 1865번 웜홀 (0) | 2022.06.04 |
백준 12851번 숨바꼭질2 (0) | 2021.06.22 |
백준 1937번 욕심쟁이 판다 (0) | 2021.06.20 |
백준 1956번 운동 (0) | 2021.06.18 |