백준공부/java
[백준] 2146번 다리 만들기 (gold 3
gomduri43
2024. 5. 8. 16:48
import java.io.*;
import java.util.*;
class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Node {
int x;
int y;
int v;
int num;
public Node(int x, int y, int v, int num) {
this.x = x;
this.y = y;
this.v = v;
this.num = num;
}
}
public class Main {
static int[] numx = {-1, 1, 0, 0};
static int[] numy = {0, 0, -1, 1};
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
Queue<Point> que = new LinkedList<>();
PriorityQueue<Node> connect = new PriorityQueue<>(new Comparator<Node>() {
public int compare(Node o1, Node o2) {
return o1.v - o2.v;
}
});
int n = Integer.valueOf(br.readLine());
//맵만들기. true이면 육지
boolean[][] check = new boolean[n + 1][n + 1];
//육지 표시하기.
for (int i = 1; i <= n; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= n; j++) {
if (Integer.valueOf(st.nextToken()) == 1) {
//육지표시
check[i][j] = true;
}
}
}
//섬 번호. 순차적으로 섬마다 번호부여하기.
int island = 1;
//같은 섬끼리는 같은 번호로 초기화하기.
int[][] map = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
//육지 존재. 섬마다 번호부여하여 구분하기.
if (check[i][j]) {
map[i][j] = island;
que.clear();
que.offer(new Point(j, i));
connect.offer(new Node(j, i, 0, island));
check[i][j] = false;
while (!que.isEmpty()) {
Point temp = que.poll();
for (int k = 0; k < 4; k++) {
int ty = temp.y + numy[k];
int tx = temp.x + numx[k];
if (tx < 1 || tx > n || ty < 1 || ty > n || !check[ty][tx]) {
continue;
}
map[ty][tx] = island;
check[ty][tx] = false;
que.offer(new Point(tx, ty));
connect.offer(new Node(tx, ty, 0, island));
}
}
island++;
}
}
}
boolean[][][] visit=new boolean[island+1][n+1][n+1];
int answer = 0;
boolean find = false;
//번호로 구분된 섬끼리 연결하면서 도달한 정점이 다른 섬일때 그것이 답.
while (!connect.isEmpty()) {
Node temp = connect.poll();
visit[temp.num][temp.y][temp.x]=true;
for (int k = 0; k < 4; k++) {
int ty = temp.y + numy[k];
int tx = temp.x + numx[k];
//바다로만 이동하면됨. 굳이 같은섬 육지안에서 이동할 필요 x
if (tx < 1 || tx > n || ty < 1 || ty > n || map[ty][tx] == temp.num || visit[temp.num][ty][tx]) {
continue;
}
//바다가 아니고, 시작한 섬과 번호가 다르면 새로운 섬. 즉 다리건설
if (map[ty][tx] > 0 && map[ty][tx] != temp.num) {
answer = temp.v;
find = true;
}
//이동한 걸 각각의 섬마다 구분한여 삼중배열로 구현
else if (map[ty][tx] == 0) {
visit[temp.num][ty][tx]=true;
connect.offer(new Node(tx, ty, temp.v + 1, temp.num));
}
}
if (find) {
break;
}
}
bw.write(answer + "");
bw.flush();
}
}
//위의 삼중배열방식으로의 구현은 메모리 측면에서 사실 큰 낭비가 발생한다.
//관점을 바꾸어 보았다. 어떤 정점을 a와 b섬에서 온 다리가 지나려 한다면 a가 지났더라도, b도 지날 수
//있어야 하기에 삼중배열방식으로 구현하였지만
//자세히 생각해보면, a섬 b섬 각각에서 다리를 bfs해나가고, 만약 어떤 정점에서 다음 bfs대상이
//다른 섬번호를 갖고 있다면, 양측에서 해당점점까지 오는데 걸린 거리를 더하면 그 값이
//총 다리의 길이가 될 것이다. 또 이렇게 구현한다면, 삼중배열방식으로 섬마다 방문노드를 체크할 필요가 없다.
//전체 정점을 node( value, islandNum)을 갖는 배열로 구현하여. 각 정점마다 이 노드를 방문한
//섬의 번호와 value를 저장하고서 비교해나가면 된다. 만약 정점이 null값이면 아직 이 곳은 아무도
//오지 못한것이므로 단순히 다리를 연장하면 될 것이고. 이곳이 null이 아니면 누군가 이곳에 왔던 것.
//서로의 islandNum값을 비교해서 서로 다르다면 그 부분에서 다리를 연결하여 두 섬을 잇는 다리를
//만들 수 있을 것이다.
//위에서는 우선순위큐를 이용해서, 거리가 짧은 것부터 우선해서 거리를 구했으므로,
//처음에 나온 값이 무조건 답이다.
//하지만 아래코드는 bfs로 굴리기 때문데, 이보다 짧은 값이 존재가 가능하다. 왜냐하면,
//거리순 정렬이 아니라 들어온 순서부터 초기화 되고 진행되기 때문이다.
//visit노드의 배열의 초기화 측면에서는 처음 섬들의 정점값 부터 진행하므로 상관이 없지만,
//섬들간의 다리 연결에서는 충분히 뒤에 나오는 다리가 더 짧은 가능성이 존재한다.
//따라서, connect 큐가 빌때까지, 그 값들을 비교하여 최솟값을 출력해야한다.
import java.io.*;
import java.util.*;
class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
class Node {
int v;
int num;
public Node(int v, int num) {
this.v = v;
this.num = num;
}
}
public class Main {
static int[] numx = {-1, 1, 0, 0};
static int[] numy = {0, 0, -1, 1};
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
StringTokenizer st;
Queue<Point> que = new LinkedList<>();
Queue<Point> connect=new LinkedList<>();
int n = Integer.valueOf(br.readLine());
//맵만들기. true이면 육지
boolean[][] check = new boolean[n + 1][n + 1];
//육지 표시하기.
for (int i = 1; i <= n; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 1; j <= n; j++) {
if (Integer.valueOf(st.nextToken()) == 1) {
//육지표시
check[i][j] = true;
}
}
}
//섬 번호. 순차적으로 섬마다 번호부여하기.
int island = 1;
//같은 섬끼리는 같은 번호로 초기화하기.
int[][] map = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
//육지 존재. 섬마다 번호부여하여 구분하기.
if (check[i][j]) {
map[i][j] = island;
que.clear();
que.offer(new Point(j, i));
connect.offer(new Point(j,i));
check[i][j] = false;
while (!que.isEmpty()) {
Point temp = que.poll();
for (int k = 0; k < 4; k++) {
int ty = temp.y + numy[k];
int tx = temp.x + numx[k];
if (tx < 1 || tx > n || ty < 1 || ty > n || !check[ty][tx]) {
continue;
}
map[ty][tx] = island;
check[ty][tx] = false;
que.offer(new Point(tx, ty));
connect.offer(new Point(tx, ty));
}
}
island++;
}
}
}
Node[][] visit=new Node[n+1][n+1];
int answer = Integer.MAX_VALUE;
//번호로 구분된 섬끼리 연결하면서 도달한 정점이 다른 섬일때 그것이 답.
while (!connect.isEmpty()) {
Point temp = connect.poll();
if(visit[temp.y][temp.x]==null){
visit[temp.y][temp.x]=new Node(0,map[temp.y][temp.x]);
}
Node node=visit[temp.y][temp.x];
for (int k = 0; k < 4; k++) {
int ty = temp.y + numy[k];
int tx = temp.x + numx[k];
//바다로만 이동하면됨. 굳이 같은섬 육지안에서 이동할 필요 x
if (tx < 1 || tx > n || ty < 1 || ty > n || map[ty][tx]==node.num ) {
continue;
}
//아직 온 게 없는 경우.->방문배열 초기화 및 que에 추가
if(visit[ty][tx]==null){
visit[ty][tx]=new Node(node.v+1, node.num);
connect.offer(new Point(tx, ty));
}
//null이 아니란건 어디선가 이쪽으로 온 정점이 존재하는 것. 이는 해상위일수도.
//바다가 아니고, 시작한 섬과 번호가 다르면 새로운 섬. 즉 다리건설
//한쪽에서 오다가, 다른쪽에서 오던 것과 만나는 케이스. 혹은 한쪽에서 다른쪽으로.
//이 경우 양쪽에서 만들던 다리의 길이를 더하면 연결이됨.
else if(visit[ty][tx].num!=node.num ){
int mid=node.v+visit[ty][tx].v;
answer= answer> mid ? mid : answer;
}
}
}
bw.write(answer==Integer.MAX_VALUE ? "0" : answer+"");
bw.flush();
}
}
결과적으로, 메모리는 1/5가량으로 줄고, 시간도 거의 20%이상 단축 되었다. 코드 길이는 주석의 여부 차이라, 실질적으로. 위 방식은 3559, 아래는 3354바이트이다.