알고리즘 문제/백준

[백준 - Java] 1325번 : 효율적인 해킹 (자바는 시간초과!!!!)

건복치 2021. 1. 18. 10:47
반응형

문제

www.acmicpc.net/problem/1325

 

1325번: 효율적인 해킹

첫째 줄에, N과 M이 들어온다. N은 10,000보다 작거나 같은 자연수, M은 100,000보다 작거나 같은 자연수이다. 둘째 줄부터 M개의 줄에 신뢰하는 관계가 A B와 같은 형식으로 들어오며, "A가 B를 신뢰한

www.acmicpc.net

설명

스캐너로 입력받고, LinkedList로 누가누가 신뢰하는지 정점 연결해주고, dfs 재귀로 돌려서 구현했다.

dfs 돌면서 가장 많은 정점(컴퓨터)을 거친 컴퓨터가 한 번에 가장 많은 컴퓨터를 해킹할 수 있는 컴퓨터가 됨.

시간 초과

 

버퍼 리더, 라이터 써서 입출력을 받았다.

시간 초과

 

누가 링크드 리스트 쓰면 시간 초과 난 데서 어레이 리스트로 변경

시간 초과

 

그리고 이 문제 해결한 스터디 팀원은 자기 코드가 맞았습니다! 가 뜨긴 하는데 가끔 시간 초과도 뜬다고 함;;;같은 코든데...

 

또!!! C++인가 암튼 자바 말고 다른 언어로 푼 사람들은 자바보다 훨씬 빠른 속도로 정답 처리됨

자바만 시간 초과가 오지게 나는 듯

 

뭐 때문인지 모르겠다 이마리야!!!!!!

 

그래도 다른 블로그의 포스팅과 스터디 팀원의 코드를 분석한 결과로 성공하긴 했다.

그리고 내 코드는 왜 비슷한데 안되는지 한 번 추측해보았다.

 

우선 시간 초과 나는 이전 코드 (시간 초과 코드)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.StringTokenizer;

public class Main {
	public static ArrayList<Integer>[] adjList;
	public static boolean[] visited;
	public static int[] result;
	public static int count = 0;
	public static int max = 0;

	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 = new StringTokenizer(br.readLine());

		int N = Integer.parseInt(st.nextToken());
		int M = Integer.parseInt(st.nextToken());

		adjList = new ArrayList[N + 1];
		result =  new int[N+1];

		// 인접 리스트 만들기 
		for (int i = 0; i <= N; i++) {
			adjList[i] = new ArrayList<Integer>();
		}

		// 인접 리스트에 신뢰 컴퓨터 관계 넣어주기 
		for(int i = 0; i < M; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			adjList[b].add(a);
		}

		// 1번 컴퓨터부터 dfs로 인접한 신뢰 컴퓨터 찾기 
		for(int i = 1; i <= N; i++) {
			visited = new boolean[N + 1];
			dfs(i);
			//System.out.println("\ni : " + i + ", value : " + count + "\n");
			result[i] = count;
			max = Math.max(max, count);
			count = 0;
		}

		// max값 가진 컴퓨터 출력 
		for(int i = 1; i <= N; i++) {
			if(result[i] == max) {
				bw.write(i + " ");
			}
		}

		bw.flush();
		bw.close();
	}

	public static void dfs(int v) {
		visited[v] = true;
		count++;
		//System.out.print(v + " ");

		Iterator<Integer> iter = adjList[v].listIterator();
		while (iter.hasNext()) {
			int w = iter.next();
			if (!visited[w]) { 
				dfs(w);
			}
		}
	}

}

 

1번 예제를 넣고 돌려보면

아래와 같이 콘솔에 찍힌다.

 

A가 B를 신뢰하는 경우에는 B를 해킹하면, A도 해킹할 수 있기에 B->A로 연결 리스트를 만들었다. (ex. adjList [b]. add(a);)

dfs를 돌면서 인접한 정점을 만날 때마다 count변수를 +1 해주고, 이를 바탕으로 4개의 컴퓨터를 해킹시키는 1번과 2번을 정답으로 출력

 

1 3 4 5 
i : 1, value : 4

2 3 4 5 
i : 2, value : 4

3 4 5 
i : 3, value : 3

4 
i : 4, value : 1

5 
i : 5, value : 1

1 2 

 

성공한 스터디 팀원의 코드 (성공 코드)

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class Main {
	
	static int[] computer;

	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 = new StringTokenizer(br.readLine());
		
		int N = Integer.parseInt(st.nextToken());
		int M = Integer.parseInt(st.nextToken());
		
		ArrayList<Integer>[] list = new ArrayList[N + 1];
		
		for (int i = 1; i <= N; i++) {
			list[i] = new ArrayList<>();
		}
		
		for (int i = 0; i < M; i++) {
			st = new StringTokenizer(br.readLine());
			
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			
			list[a].add(b);
		}
		
		//각 컴퓨터(정점)별로 해킹할 수 있는 컴퓨터 수를 셈 -> 그 중에 최대값을 가진 컴퓨터를 출력
		computer = new int[N + 1];
		int max = Integer.MIN_VALUE;
		for (int i = 1; i <= N; i++) {
			boolean[] visited = new boolean[N + 1];
			dfs(i, visited, list);
			System.out.println();
		}
		
		//최대로 해킹할 수 있는 컴퓨터 수를 구함
		for (int i = 1; i <= N; i++) {
			max = Math.max(max, computer[i]);
		}
		
		//가장 많은 컴퓨터를 해킹할 수 있는 컴퓨터가 여러개일수도 있기 때문에 for문을 돌면서 각 컴퓨터 번호를 출력
		for (int i = 1; i <= N; i++) {
			//System.out.println("i : " + i + ", value : " + computer[i] + "\n");
			if (computer[i] == max) {
				bw.write(i + " ");
			}
		}
		
		bw.flush();
		bw.close();
	}
	
	public static void dfs(int start, boolean[] visited, ArrayList<Integer>[] list) {
		visited[start] = true;
		//System.out.print(start + " ");

		for (int node : list[start]) {
			if (!visited[node]) {
				computer[node]++;
				dfs(node, visited, list);
			}
		}
		
	}

}

 

1 
2 
3 1 2 
4 3 1 2 
5 3 1 2 
i : 1, value : 3

i : 2, value : 3

i : 3, value : 2

i : 4, value : 0

i : 5, value : 0

1 2 

 

 

dfs로 똑같이 인접한 정점들을 돌지만 나처럼 count 변수를 ++ 시키는 것이 아니라 아예 인덱스에 해당하는 배열의 값을 ++해줌

그리고 나는 B->A로 인접 리스트에 넣어주었는데 A->B로 인접리스트에 넣어줌.

왜 저렇게 하면 되는 거지....? 내 코드에서 어떤 점이 팀원의 코드보다 더 시간이 걸리는 요인이 되었던 걸까...

 

굳이 따져본다면 팀원은 값이 3 + 3 + 2로 ++횟수가 8이라면

나는 4 +4 + 3 + 1 + 1로 카운트 횟수가 13이라설까....?

이렇게 카운트해주는 것도 시간 복잡도에 그렇게 많은 영향을 미치나?

모르겠다ㅜㅜㅜㅜ

 

암튼 성공한 팀원의 코드를 좀 더 정리해보겠다.

 

설명

팀원은 나처럼 B->A가 아니라 A->B로 인접 리스트를 구현했다.

첨에 왜 저렇게 넣는 거지? 생각했는데 조삼모사다.

 

dfs를 통해 시작 정점부터 탐색을 한다.

dfs함수에서 dfs를 다시 호출하는 즉, 정점에 대해 재귀 함수가 호출되는 횟수는 해킹할 수 있는 컴퓨터의 개수와 동일하다는 것이다.

 

자세한 내용은 https://mygumi.tistory.com/337 이 블로그를 참고하면 좋을 것 같다!!!

 

즉! 시작 정점을 지나 탐색되는 인접한 정점들은 시작 정점을 해킹할 수 있다.

1번 입력 예시를 바탕으로 예를 들어보자.

5번 정점을 시작으로 dfs를 돌면서 인접한 3번 정점에 들렸다. -> 3번 정점으로 5번 정점은 해킹될 수 있다. (5번이 3번을 신뢰하닌까!)

5번 정점을 시작으로 dfs를 돌면서 인접한 1번 정점에 들렸다. -> 1번 정점으로 5번 정점은 해킹될 수 있다.

5번 정점을 시작으로 dfs를 돌면서 인접한 2번 정점에 들렸다. -> 2번 정점으로 5번 정점은 해킹될 수 있다. 

 

public static void dfs(int start, boolean[] visited, ArrayList<Integer>[] list) {
		visited[start] = true;
		//System.out.print(start + " ");

		for (int node : list[start]) {
			if (!visited[node]) {
				computer[node]++;
				dfs(node, visited, list);
			}
		}
		
	}

 

dfs 재귀 호출 전 총 N개의 크기를 가진 computer 배열에 인접한 정점에 해당하는 인덱스의 값을 +1 해준다.

이렇게 ++해주면서 전체 정점을 확인하면 각 컴퓨터가 해킹할 수 있는 컴퓨터의 개수를 파악할 수 있다.

 

GITHUB

github.com/KwonMinha/BOJ/blob/master/BOJ%231325/src/Main.java

 

KwonMinha/BOJ

Baekjoon Online Judge(Java) 문제풀이. Contribute to KwonMinha/BOJ development by creating an account on GitHub.

github.com

 

반응형