🐙 문제 

https://www.acmicpc.net/problem/1707

 

이 문제는 " 이분 그래프 " 의 뜻을 잘 이해해야한다. 

그래프의 정점의 집합을 둘로 분할하여, 각 집합에 속한 정점끼리는 서로 인접하지 않도록 분할할 수 있을 때, 그러한 그래프를 특별히 이분 그래프 라 부른다. 

 

쉽게 설명하자면,  그래프의 노드들을 바구니 A와 바구니 B에 나눠서 담는다고 하자. 

이때 같은 바구니에 담긴 노드들끼리는 인접하면 안된다는 의미이다.

 

 

그래프가 예시로 주어졌을때,  노드를 번갈아 바구니 A,B에 나누어서 담아보면 된다. 

 

 

[입력예시] 

2   // 테스트 케이스 개수 
3 2 // case1 : 노드의 개수, 엣지의 개수 
1 3
2 3
4 4 // case2 : 노드의 개수, 엣지의 개수 
1 2
2 3
3 4
4 2

 

 

 

🐙 풀이과정 

 

이분 그래프인 case 

 

 

첫번째 케이스를 그림으로 나타내면 아래와 같다 

 

그리고 바구니 A,B 를 만든다. 

먼저 바구니 A에 노드 1을 담는다. ( A,B 아무거나 상관없음) 

 

노드3으로 이동하여, 현재 바구니의 값과 반대인 바구니 B에 담는다. ( 바구니는 A,B 두개 밖에 없기 때문에 A 가 아니라면 B에 담아야한다) 

 

방문하지 않은 노드2로 이동하여, 현재 바구니 값(B)와 반대인 바구니  A에 값을 담는다 

 

 

노드 1에서 시작하여  노드들을 바구니에 번갈아 가며 담았을때, 위의 그림과 같이 구분된다. 

 

결과적으로 같은 바구니A에 담긴 노드1,2는 인접하지 않는다. 

 

이러한 과정을 노드2에서 시작, 노드3에서 시작하더라도 항상 같은 바구니에 있는 노드들은 인접하지 않는다. 

 

  • 노드 2에서 시작한 경우 

 

  • 노드3에서 시작한 경우 

 

 

따라서 이 그래프는 이분 그래프 라고 할 수 있다. 

잘 이해가 안간다면 이분 그래프가 안되는 경우를 보고 이해해보자

 

 

이분 그래프가 아닌 case 

 

문제의 예시2번째는 이분 그래프가 아닌 경우이다. 

 

왜냐하면 위의 그림에서 나와있듯이,  2 → 3 → 4 가 사이클을 형성하고 있다. 

 

그런데 4 → 2 의 경우, B에 담긴 노드4가  B에 담긴 노드2와 인접하고 있다. 즉, 같은 바구니에 담긴 노드들이 인접했다. 

따라서 이 경우는 이분 그래프가 될 수 없다. 

 

그렇다면 어떤 상황에 같은 바구니에 담긴 노드들이 서로 인접할까? 

 

정답은 다음 경로에 있는 노드가, 현재 노드의 바구니와 동일한 경우이다. 

현재 노드가 바구니 A에 담겨있는데, 다음으로 탐색할 노드도 바구니A에 담겨있다면 이 경우 이분 그래프의 정의에 위배되는 것이다. 

 

 


 

  • 임의의 노드 한점에서 DFS를 수행한다. ( 중요한 것은 모든 노드에서 수행해야한다는것!) 
  • 아직 방문하지 않은 노드라면, 현재 바구니의 값과 다른 바구니의 값으로 설정한다.
  • 이미 방문한 노드라면, 해당 노드의 바구니의 값을 확인하여 현재 바구니의 값과 일치한다면 No를 출력, 현재 바구니의 값과 일치하지 않는다면 Yes를 출력한다. 

 

 

🐙 코드 

package 고자프_수업정리.그래프;

import java.io.*;
import java.util.*;

public class P48_이분그래프 {

    static ArrayList<Integer>[] list; // 그래프 저장
    static StringBuilder sb; // 결과를 저장
    static String[] visited; // 방문 상태 저장
    static final String NOT_VISITED = "N"; // 초기값 설정
    static boolean isBipartiteGraph; // 이분 그래프 여부 확인 플래그

    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;

        int k = Integer.parseInt(br.readLine()); // 테스트 케이스 개수
        sb = new StringBuilder();

        for (int i = 0; i < k; i++) {
            st = new StringTokenizer(br.readLine());
            int V = Integer.parseInt(st.nextToken()); // 정점 개수
            int E = Integer.parseInt(st.nextToken()); // 간선 개수

            // 그래프 초기화
            list = new ArrayList[V + 1];
            visited = new String[V + 1];
            for (int j = 1; j <= V; j++) {
                list[j] = new ArrayList<>();
                visited[j] = NOT_VISITED;
            }

            // 그래프 생성
            for (int j = 0; j < E; j++) {
                st = new StringTokenizer(br.readLine());
                int u = Integer.parseInt(st.nextToken());
                int v = Integer.parseInt(st.nextToken());

                // 무방향 간선 추가
                list[u].add(v);
                list[v].add(u);
            }

            // 이분 그래프 판별
            isBipartiteGraph = true; // 기본값
            // 주어진 그래프가 연결그래프라는 보장 x -> 모든 노드에서 수행하기 
            for (int j = 1; j <= V; j++) {
                if (visited[j].equals(NOT_VISITED)) {
                    checkBipartiteGraph(j, "A");
                    if(!isBipartiteGraph) break; 
                }
            }
            
            // 결과 저장
            sb.append(isBipartiteGraph ? "YES" : "NO").append("\n");
        }

        // 결과 출력
        bw.write(sb.toString());
        bw.flush();
        bw.close();
        br.close();
    }

    static void checkBipartiteGraph(int x, String group) {
        visited[x] = group; // 현재 노드를 방문 표시
        String opposite = group.equals("A") ? "B" : "A"; // 반대 그룹 설정

        for (int num : list[x]) {
            if (visited[num].equals(NOT_VISITED)) { // 아직 방문하지 않은 노드
                checkBipartiteGraph(num, opposite);
            } else if (visited[num].equals(group)) { // 동일 그룹에 속할 경우
                isBipartiteGraph = false; // 이분 그래프가 아님
                return;
            }
        }
    }
}

 

 

🧐 무방향 그래프로 저장하는 이유? 

간선의 방향이 입력에서 명확히 주어지지 않으면 양방향 그래프로 처리하자. 

 

🧐 모든 노드에서 탐색을 시작해야하는 이유? 

문제의 조건에서 모든 노드들이 하나로 연결되어있다고 생각할 만한 부분은 없었기 때문에, 임의의 한 노드에서만 탐색을 하여서  이분 그래프인지를 따져선 안된다.  모든 노드에서 확인을 해줘야한다. 

 

그래서 boolean isBipartiteGraph = true; 라는 변수를 이용하여 한번이라도 false 값으로 초기화 되면 , 그 그래프는 이분 그래프가 될 수 없도록 하는 것이다. 

 

 

 


🐙 정리 

이 문제는 간단히 정리하자면, 그래프를 이용한 DFS 응용문제이다.  

왜 이 문제에서 왜 DFS를 사용하였고, 왜 두 그룹으로 나누는지에 대해서 생각해보면 좋을 것 같다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 1929 소수 구하기 자바  (1) 2024.11.19
[백준] 11047 동전0 자바  (0) 2024.11.18
[백준] 1167번 트리의 지름  (0) 2024.10.30
[백준] 11005 진법 변환2 자바  (2) 2024.10.03

문제 

[백준] 1929번 소수구하기 문제

 

방법1- 일반적인 소수 구하는 방법으로 풀기  

import java.io.*;
import java.util.StringTokenizer;

public class Main {

    static StringBuilder sb;

    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 M = Integer.parseInt(st.nextToken());
        int N = Integer.parseInt(st.nextToken());

        // 기본적인 방법으로 소수 판별하기
        sb = new StringBuilder();
        for(int i=M;i<=N;i++){
            checkPrimeNumber(i);
        }

        // 결과 출력
        bw.write(String.valueOf(sb));
        bw.flush();

        br.close();
        bw.close();
    }

    // 소수인지 판별하는 메서드
    public static void checkPrimeNumber(int num){
        if(num<=1) return;
        for(int i=2;i<= Math.sqrt(num);i++){
            if(num%i==0) return; // 소수가 아님
        }
        // 소수인 경우
        sb.append(num).append("\n");
    }


}

 

일반적인 방법으로 푼 경우

 

 

방법2- 에라토스테네스의 체 방법 적용하기 

import java.io.*;
import java.util.StringTokenizer;

public class Main {
    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 M = Integer.parseInt(st.nextToken());
        int N = Integer.parseInt(st.nextToken());

        boolean[] arr = new boolean[N+1];

        // 배열 초기화
        for(int i=2;i<=N;i++){
            arr[i]=true;
        }

        // 에라토스테네스의 체
        for(int i=2;i<=Math.sqrt(N);i++){
            if(!arr[i]) continue; // 이미 지워진 수는 건너뛰기
            for(int j=i+i;j<=N;j+=i){ // 배수 지우기
                if(!arr[j]) continue; 
                arr[j]=false;
            }
        }

        // 남은수 -> 소수 출력하기
        for(int i=M;i<=N;i++){
            if(arr[i]){
                bw.write(i+"\n");
            }
        }
        br.close();
        bw.flush();
        bw.close();

    }
}

 

 

에라토스테네스의 체 알고리즘 적용 후

 

 

 

 

 

 

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 1707 이분 그래프 자바  (0) 2024.11.22
[백준] 11047 동전0 자바  (0) 2024.11.18
[백준] 1167번 트리의 지름  (0) 2024.10.30
[백준] 11005 진법 변환2 자바  (2) 2024.10.03

문제 

https://www.acmicpc.net/problem/11047

 

 

 

동전의 종류와 금액이 주어질때, 해당 동전들을 가지고 금액을 만들기 위한 동전의 최소 개수를 구하는 문제이다. 

동전의 개수가 최소가 되어야하므로 가장 큰 값들로 먼저 계산하면 최솟값을 구할 수 있을것이다. 

 

import java.io.*;
import java.util.StringTokenizer;

public class P32_동전개수의최솟값 {
    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 total = Integer.parseInt(st.nextToken()); // 금액
        int count=0; // 필요한 동전의 개수

        int[] money = new int[n];
        for(int i=0;i<n;i++){
            money[i] = Integer.parseInt(br.readLine());
        }

        // 가장 큰 금액부터 계산하기
        for(int i=money.length-1;i>=0;i--){
            if(total==0) break;
            count += (total / money[i]);
            total%=money[i];
        }
        bw.write(count+" ");

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

 

 

🐟 코드 수정할 부분 

큰 값들로 먼저 몫을 구해야 최소의 개수가 나오기 때문에 반복문을 이용해서 큰값 부터 차례대로 나누어서 계산하는 코드를 작성했다. 

위의 코드로 작성하더라도 상관은 없지만,  아래의 코드를 사용한다면 의미 없는 반복이 있을 수 있다는 문제점이 있다. 

 // 가장 큰 금액부터 계산하기
        for(int i=money.length-1;i>=0;i--){
            if(total==0) break;
            count += (total / money[i]);
            total%=money[i];
        }

 

예를들어, 아래와 같은 입력이 주어졌다고 하자.

10 4200 // 동전의 종류 수, 금액
1
5
10
50
100
500
1000
5000
10000
50000

 

위의 for문에 따르면 가장 큰 금액 순서대로 계산이 되기 때문에 50000 → 10000 →   5000 →   1000 →  ..... 순으로 계산이 된다. 

그런데 total= 4200 이기 때문에 4200 보다 큰 50000,10000,5000은 계산 할 필요가 없다. 

 

결국, total값 보다 큰 값으로는 나눌 수 없기 때문에 단순히 반복문을 이용해서 구현한다면 의미 없는 연산이 반복된다. 

 

따라서 " total >= money[i]  "  인 경우에만 연산을 수행하도록 조건을 추가해주면 더욱 효율적인 코드가 될 것 같다. 

 

 

🐙  수정한 코드 

for(int i=money.length-1;i>=0;i--){
    if(total==0) break; // total값이 0이 되면 바로 종료하기
    else if (total>=money[i]) {
        count += (total / money[i]);
        total %=money[i];
    }
}

 

 

🪸 두 코드의 실행시간 비교 

 

 

 

밑에 있는 결과가 첫번째 코드, 위에 있는 결과가 수정 후 코드이다. 역시 수정 후 코드가 조금 더 빠르다. 

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 1707 이분 그래프 자바  (0) 2024.11.22
[백준] 1929 소수 구하기 자바  (1) 2024.11.19
[백준] 1167번 트리의 지름  (0) 2024.10.30
[백준] 11005 진법 변환2 자바  (2) 2024.10.03

 

트리의 지름이란, 트리의 노드 중에서 가장 먼 두 정점사의 거리 혹은 경로를 의미한다. 

 

트리의 지름을 구하는 방법은 아래와 같다. 

  1. 임의의 정점 x에서 가장 먼 정점 y를 찾는다
  2. 정점 y에서 가장 먼 정점 z를 찾는다
  3. 정점 y ~ 정점 z 사이의 거리를 구한다

 

이 문제의 핵심은 정점 y ~ 정점 z 가 트리의 지름이 되는 이유를 이해하는 것이다. 

 

정점 y~ 정점z가 트리의 지름의 양 끝점이 된다는 말의 의미는, 임의의 한점에서 찾은 가장 먼 점은 항상 지름의 양 끝점 중 하나라는 뜻이다. 

따라서 그 점에서 가장 멀리 있는 점은 트리의 양 끝점의 또 다른 한 점 이므로, 두 정점 사이의 거리가 트리의 지름이 되는것이다. 

 

임의의 한점에서 가장 멀리 있는점은, 트리 지름의 양 끝점중 하나이다. 

 

 

 

 

임의의 한 점에서 가장 멀리 있는 점은, 항상 트리 지름의 양끝점 중 한점이 될까? 

 

정점 u와 정점v를 연결하는 경로가 트리의 지름이라고 하고,  임의의 정점x에서 가장 먼 정점은 y라고 할때, 

이 정점들의 관계는 3가지로 표현될 수 있다. 

 

  1. x가 u또는 v인 경우
  2. y가 u또는 v인 경우 
  3. x,y 그리고 u,v가 모두 다른 정점인 경우 

1,2의 경우를 생각해보자. x에서 가장 먼 점은 y이다.

만약 x가 u또는 v라면, x에서 가장 먼점은 트리의 지름중 나머지 한 점이 된다. 2번의 경우도 마찬가지이다. 

 

문제는 3번의 경우이다. 

3의 경우 두 가지 경우로 나누어서 생각할 수 있다. 

 

 

a) x~y의 경로와 u~v 경로가 한 점이상을 공유하는 경우 (경로가 겹치는 경우) 

 

d(a,b)를 a에서부터 b까지의 거리라고 하자. 

 

 

정점 x에서 가장 먼 정점은 y이다. 이 말의 뜻은 점 t를 기준으로 x에서 가장 멀리 떨어져있는 점이 y라는 뜻이다 

따라서 t~y까지의 거리가 u~t 또는 t~v까지의 거리 보다 크거나 같아야 , x에서 가장 먼 점이 y가 될 수 있다. 

 

이를 수식으로 표현하면  d(t,y) >= max(d(u,t),d(t,v)) 이다. 

 

  •  d(t,y) > max(d(u,t),d(t,v)) 인 경우 

정점 u에서 가장 먼점은 v이다. 이 말의 뜻은 점t가 갈 수 있는 경로중 가장 멀리 있는 점이 v라는 것인데 , 위의 가정에 따르면 t에서 가장 멀리 있는 점은 y가 되어야한다. 따라서 이 경우는 존재할 수 없다. 

  •  d(t,y) = max(d(u,t),d(t,v)) 인 경우 

이 가정이라면 정점x에서 가장 먼 정점은 y가 될 수 있고, 정점u에서 가장 먼 정점은 v가 될 수 있다. 

 

∴  y는 u 또는 v 이다. 

 

 

 

 

b)  x~y의 경로와 u~v 경로가  독립적인 경우 

 

 

이 그림에서 x~y와 u~v가 독립적인 경로라고 하였는데, 두 경로를 연결해놓은 이유는 트리의 특징 때문이다. 
트리는 사이클이 없이 모든 노드가 연결된 그래프이다. 모든 정점은 루트 노드를 통해 연결된다.
따라서 트리의 어딘가에 이 두 경로가 만나는 공통 조상 노드가 반드시 존재하게된다. 

 

 

정점 x에서 가장 먼 정점이 y라면, d5 >= d3+ max(d1,d2) 임을 알 수 있다. 

또한 u에서 가장 먼 점이 v라는 것은 d2 >= d3+ max(d4,d5) 임을 알 수 있다. 

그런데 이 두 식은 동시에 성립할 수 없다. 첫번째 식에 따르면 d5>d2 이고, 두번째 식에 따르면 d2>d5이다. 

따라서 이 경우는 존재할 수 없다. 

 

 

 

 

결론

(트리 구조에서) 임의의 한 점에서 가장 멀리 있는 점은, 항상 트리 지름의 양끝점 중 한점이다. 

 

 

출처

https://velog.io/@zioo/트리의-지름-구하기

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 1707 이분 그래프 자바  (0) 2024.11.22
[백준] 1929 소수 구하기 자바  (1) 2024.11.19
[백준] 11047 동전0 자바  (0) 2024.11.18
[백준] 11005 진법 변환2 자바  (2) 2024.10.03

 

 

✅문제


 

10진법으로 첫번째값으로 주어진다. 이 수를 두번째 값으로 주어진 진법으로 변환하여 출력하는 문제이다

 

 

 

 

✅풀이


 

🧁풀이과정 

 

 

예를 들어 25를 2진법으로 변환해보자. 

 

25 / 2  ===> 몫: 12 , 나머지:1 

12/2   ===>  몫:6 ,    나머지:0

6/2     ===>  몫:3 ,   나머지: 0

3/2    ===>  몫:1,    나머지: 1

1/2    ===>  몫:0,    나머지:1 

 

몫이 0이 되었을때 나머지 값들을 역순으로 읽는다 11001 

 

 

10 진법의 수를 몫이 0이 되기 전까지 계속 n 의 값으로 나누면서 나머지를 저장한다. 

그리고 몫이 0이 되었을때 역순으로 값을 읽는다. 

 

import java.io.*;
import java.util.StringTokenizer;

public class Main {
    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());
        StringBuilder sb = new StringBuilder();

        int num = Integer.parseInt(st.nextToken());
        int n = Integer.parseInt(st.nextToken());

        while(num>0){

            int k=num%n; // 나머지
            num=num/n;  // 몫

            if(k>=10){ // 나머지 10이상인 경우 문자로 치환
                sb.append((char)(k+55));
            }
            else {
                sb.append(k);
            }
        }

        // 역순으로 출력
        bw.write(sb.reverse().toString());

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

 

 

 

처음에 몫=0 이 되었을때 값을 역순으로 읽기 때문에 스택에서 값을 빼내는 방식으로 생각했었다. 하지만 스택에 넣은 값을 빼내는 것도 좋지만 문자열에 넣고 값을 거꾸로 꺼내는 방식도 괜찮은 것 같다. 

 

 

✅정리 


 

StringBuilder 에서 reverse 메서드를 사용하면 값을 거꾸로 꺼낼 수 있다.  이후 toString 메서드를 이용하여 문자열로 바꿔서 출력하면 된다. 

 

 

 

 

 

'알고리즘 > 백준' 카테고리의 다른 글

[백준] 1707 이분 그래프 자바  (0) 2024.11.22
[백준] 1929 소수 구하기 자바  (1) 2024.11.19
[백준] 11047 동전0 자바  (0) 2024.11.18
[백준] 1167번 트리의 지름  (0) 2024.10.30

+ Recent posts