[문제링크]

 

15685번: 드래곤 커브

첫째 줄에 드래곤 커브의 개수 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 드래곤 커브의 정보가 주어진다. 드래곤 커브의 정보는 네 정수 x, y, d, g로 이루어져 있다. x와 y는 드래곤 커

www.acmicpc.net

 

0. N세대 드래곤 커브란? N-1세대 드래곤 커브를 끝 점 기준 90도 회전시켜 N-1세대의 끝 점에 붙인 것

  - 한 세대마다 2배씩 증가하지만, 세대가 최대 10이므로 최대 pow(2,10), 1024개의 지점으로 구성된다

 

1. N세대 드래곤 커브를 만들기 위해선?

  - N-1세대의 끝 점에서 시작해, N-1세대 커브의 구성을 역순으로 90도 회전시켜서 적용한다

2세대 커브

  - 예를들어, 위 2세대 커브는 ↑ 순서로 이루어져 0, -2 지점에서 종료되었다.

  - 3세대 커브를 만들려면 0, -2 지점에서 2세대 커브의 역순인 → 이동을, 90도 회전시켜 ↑ 순서로 진행하면 된다

3세대 드래곤 커브

  - 이는 문제 예시를 통해 확인 가능하다

 

2. 해당 과정을 위해 N세대까지 거쳐온 기록을 Arraylist로 저장하고, 이를 역순으로 탐색하며 끝점을 이동시킨다

 

3. 끝점은 분명한 경로를 가지고 움직이지만, 문제의 요구사항은 0<=x,y<=100 인 (x,y) 좌표에 대해서 정사각형을 구하는 것이므로, 끝 점이 방문한 좌표만 기록한다

 

4. 모든 커브가 그려진 후, 0~100의 좌표들에 대해서 사각형 조건 충족 여부를 검사, 출력한다

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

public class Main{
	
	static int[][] dir= {
			{0,1},
			{-1,0},
			{0,-1},
			{1,0}
	};
	
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int cntSquare=0;
		boolean[][] map = new boolean[101][101];
		
		int N = pint(br.readLine());
		for (int i = 0; i < N; i++) {

			StringTokenizer st = new StringTokenizer(br.readLine(), " ");
			int y = pint(st.nextToken());
			int x = pint(st.nextToken());
			int d = pint(st.nextToken());
			int g = pint(st.nextToken());
			
			if(inBox(x,y)) {
				map[x][y]=true;
			}
			x+=dir[d][0];
			y+=dir[d][1];
			if(inBox(x,y)) {
				map[x][y]=true;
			}
			//curve의 경로 저장
			List<Integer>curve = new ArrayList<>();
			curve.add(d);
			
			for (int j = 0; j < g; j++) {
				for (int j2 = curve.size()-1; j2 >= 0; j2--) {
					//curve를 역순으로 탐색
					int curDir = curve.get(j2);
					//90도 회전
					int nextDir=(curDir+1)%4;
					//끝 점 이동 적용
					x+=dir[nextDir][0];
					y+=dir[nextDir][1];
					if(inBox(x,y)) {
						map[x][y]=true;
					}
					curve.add(nextDir);
				}
			}
			
		}
		
		//조건을 만족하는 사각형의 갯수 찾기
		for (int i = 0; i <100; i++) {
			for (int j = 0; j < 100; j++) {
				if(map[i][j] && map[i][j+1] && map[i+1][j] && map[i+1][j+1]) {
					cntSquare++;
				}
			}
		}
		System.out.println(cntSquare);
	}
	
	static boolean inBox(int x, int y) {
		if(x>=0 && x<=100 && y>=0 && y<=100)return true;
		return false;
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
	
}

결과 화면

 

※ 2024. 09. 01 추가사항 

 

파판 14 7.0 패치에 파판 9 관련 오마주가 많을 예정이라 그런지, 살짝 파판9 유저가 늘어난 느낌이더라구요

 

마침 모그리모드도 9.0으로 버전업되고, UnityEx 버전도 여러번 변경되어 관련 내용 최신화를 진행할 필요를 느꼈습니다

 

이에 기본 파판 9 게임에서 모그리모드와 한글패치까지 진행하는 과정을 영상 + 설명 자막으로 만들었습니다

 


아래 내용들은 24.09 이전 작성된 내용으로, 현 시점에서 다르게 진행되는 경우가 있습니다.

참고용으로 내용은 유지하지만, 영상 위주로 진행 바랍니다.


 

 

※ 모그리 모드란?

  • HD 배경, 텍스쳐 지원 (핵심)
  • 다양한 버그 수정
  • 화면 비율 조정 및 폰트변경
  • 전투 속도 조정

  등 다양한 기능을 제공해주는 파판 9 모드입니다. 

 

 

※ 기존 파판9 한글패치에 대해

  • 파판9 한글패치가 존재하지만 2017년 초 이후 업데이트가 없는 상태
  • 파판9 게임이 추후 업데이트되며 기존 패치 파일들에서 문제 발생
  • 기존에도 모그리 모드와 직접 호환되지는 않았던것으로 추정

 위와 같은 이유로 2021년 현재 애로사항이 많아, 제가 해결한 방법을 공유합니다.

 

 

1. 준비물

 

2. 모그리 모드 설치

  • 파이널판타지 9 게임을 스팀에서 설치합니다.

 

  • 모그리 모드 파일을 다운받은 후, exe 확장자 파일을 실행해 설치합니다.
    • 게임 설치 위치를 자동으로 탐색하지만, 찾지 못할경우 파판9 게임 설치 폴더에서 실행하시면 됩니다.

 

  • 설치가 완료되면 게임 실행 화면이 다음과 같이 변경됩니다.

기본 실행화면 <-   ->  모그리 모드 실행화면

 

3. 한글패치 적용

  • 다운받은 한글패치의 압축을 풀면 총 4개의 파일로 구성되어 있습니다.

 

  • 먼저, resources.assets 파일을 추출해야합니다.
  • UnityEX 프로그램을 실행한 후, 좌상단의 Open archive Unity로 resources.assets를 불러옵니다.

 

  • 우클릭 - Select All - 우클릭 - Export with convert
  • 버전에 따라 Select All이 없으면 CTRL + A하지 마시고!!!! 맨 위 클릭 -> 스크롤 맨 아래로 -> SHIFT+맨 아래 클릭 으로 전체 선택합니다.
    • CTRL+A는 같은 종류의 파일만 선택해 일부 파일만 추출됩니다.

 

  • 동일한 방식으로 sharedassets2.assets도 추출합니다.

 

  • 정상적으로 완료됬다면, 한글패치 폴더 내부에 Unity_Assets_Files라는 폴더가 생기며, 내부에 방금 추출한 두 파일이 있습니다.

 

  • Unity_Assets_Files 폴더 안에 첨부된 bat파일을 넣고 실행합니다.
    • (mkdir, copy명령어만 있는 배치파일입니다. 우클릭-편집 또는 확장자를 txt로 변경해 확인 가능합니다)

FFIX.bat
0.00MB

 

  • bat파일 실행이 완료되면, 해당 폴더 안에 새로운 Unity_Assets_Files 폴더가 생성됩니다.

 

  • 스팀에서 게임 - 우클릭 - 관리 - 로컬 파일 보기 를 선택해 게임 폴더로 진입합니다.

 

  • 게임 폴더에서 64bit 윈도우라면 x64/FF9_Data 폴더로, 32bit라면 x86/FF9_Data 폴더로 이동합니다.

 

  • 위에서 만든, 새로운 Unity_Assets_Files 폴더를 붙여넣기합니다.

 

  • (중요) UnityEX를 실행해, x64/FF9_Data 내부의 resources.assets을 Open합니다.
    • 한글패치의 resources.assets파일이 아닙니다
    • 기존 resources.assets, sharedassets2.assets 파일을 복사-붙여넣기 해 백업해두는것을 추천합니다

 

  • 중앙 상단의 Import all files 버튼을 클릭합니다.

  • 동일한 방식으로 x64/FF9_Data 내부의 sharedassets2.assets 파일도 import합니다.
    • 작업이 오래 걸리고, UnityEX가 응답없음으로 뜰 수 있습니다. 기다리시면 됩니다.

 

  • 게임 실행 시 메인화면이 한글로 뜨면 성공입니다

 

  • 패치 적용 테스트 영상

 

  • 21.06 시점 게임 전체가 아닌 초반부에 한해 테스트 진행했습니다.
  • 21.12 시점 게임 클리어 완료했습니다
  • 문제 발생시 댓글 남겨주시면 확인하겠습니다.

 

21.12.08 추가

 

한글패치 + 모그리모드 적용 후 클리어했습니다. 하데스도 잡았고, 모그레터 본부도 고쳤고, 오즈마는 안잡았지만 쵸코보 이벤트도 어느정도는 진행했습니다.

발생했던 주요 버그들 / 해결법 간단하게 남기겠습니다

 

  • 랙타임 마우스 O/X 문제 해결 불가
    • 제시한 문제에 맞게 답을 했는데도 틀린다거나, 텍스트가 덮어씌워지는 문제
    • 다른 글에도 비슷한 내용이 있고 한글패치의 패치내역에도 있던 문제인데, 버전이 오르며 재발생했다고 생각됩니다.
    • 해결법 : 영문판으로 진행한다.
  • 최종보스 잡은 이후 강제종료 문제
    • 최종보스 잡은 뒤 배 탑승 동영상 재생 이후 에러코드도 없이 프로그램이 강제 종료
    • 저 혼자만의 문제인지, 모두의 문제인지는 확인하지 못했습니다 .
    • 해결법 : 모그리모드 제거 후 진행, 한글패치 유무는 영향 없었습니다

 

  • 모그리모드 제거 방법
    • Steam 우클릭 - 속성 - 로컬파일 메뉴의 게임 파일 무결성 검사 실행
    • 자동으로 변경된 파일 탐지 - 재다운로드 후 복구됩니다.
    • 위 방법으로 해결 안될시 삭제 후 재다운로드
    • 스팀 클라우드를 사용하는 게임이지만, 불안하면 게임 세이브 백업을 추천드립니다.
  • 한글패치 제거 방법
    • 백업해둔 assets파일들 복구

 

23.05.23 추가

  • 모그리 모드 및 한글패치 정상 적용 확인했습니다
    • 모그리 모드 파일, 한글 패치, UnityEX 등 다 새롭게 다운로드 받아 테스트했습니다.
  • UnityEX 버전이 변경되어 Select All이 없을 경우 해결법 추가했습니다

[문제링크]

 

1922번: 네트워크 연결

이 경우에 1-3, 2-3, 3-4, 4-5, 4-6을 연결하면 주어진 output이 나오게 된다.

www.acmicpc.net

 

0. 기초적인 MST 문제 (MST란?)

 

1. PRIM 알고리즘 사용, 최소 거리로 갈수있는 & 미방문한 노드 순서로 방문한다

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		
		int N = pint(br.readLine());
		int M = pint(br.readLine());
		
		graph = new LinkedList<>();
		for (int i = 0; i <= N; i++) {
			graph.add(new LinkedList<>());
		}
		
		for (int i = 1; i <= M; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine(), " ");
			int fst = pint(st.nextToken());
			int snd = pint(st.nextToken());
			int cost = pint(st.nextToken());
			
			graph.get(fst).add(new int[] {snd, cost});
			graph.get(snd).add(new int[] {fst, cost});
		}
		
		int totalCost=0;
		int start=1;//아무거나
		
		boolean[] isVisit=new boolean[N+1];//방문체크
		int[] cost = new int[N+1]; // 0번 PC는 없다
		Arrays.fill(cost, Integer.MAX_VALUE);
		cost[start]=0;//시작 cost 초기화
		
		for (int i = 0; i < N; i++) {
			
			int min=Integer.MAX_VALUE;
			int minNode=0;
			for (int j = 1; j <= N; j++) {
				if(!isVisit[j] && cost[j]<min) {
					min=cost[j];
					minNode=j;
				}
			}
			//가장 짧은거 연결
			isVisit[minNode]=true;
			totalCost+=min;
			
			//minNode 에서 연결되있는거 갱신
			int len = graph.get(minNode).size();
			for (int j = 0; j < len; j++) {
				int[] adjNode = graph.get(minNode).get(j);
				if(!isVisit[adjNode[0]]) {
					//방문한적 없다면
					cost[adjNode[0]] = Math.min(cost[adjNode[0]], adjNode[1]);
				}
			}
		}
		System.out.println(totalCost);
		
	}
	static LinkedList<LinkedList<int[]>>graph;
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
}

결과 화면

[문제링크]

 

1647번: 도시 분할 계획

첫째 줄에 집의 개수N, 길의 개수M이 주어진다. N은 2이상 100,000이하인 정수이고, M은 1이상 1,000,000이하인 정수이다. 그 다음 줄부터 M줄에 걸쳐 길의 정보가 A B C 세 개의 정수로 주어지는데 A번 집

www.acmicpc.net

 

0. 집 사이의 연결을 유지하며 길을 제거하고, 최소 비용의 길들을 사용해 전체를 연결하기 == 최소 신장 트리(MST)

 

1. 최소 신장 트리를 구하면 최소 거리의 길들로 모든 마을을 연결할 수 있다

 

2. 완성된 MST의 간선들 중 하나를 제거하면 2개의 tree가 생성된다

  - 마을을 2개로 분리할 수 있다

 

3. 길의 유지비의 합은 항상 최소를 유지해야하므로, MST를 이루는 간선들 중 가중치가 가장 큰 간선 하나를 제거하면 마을을 분리하면서 비용을 최소로 만들 수 있다

  - Kruskal의 경우, 간선을 기준으로 최소 간선부터 고려하므로, 마지막에 선택할 간선을 선택하지 않아도 된다

  - N개의 집이 있을때, N-1개가 아닌 N-2개만 선택하는 것

 

<Kruskal 사용>

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		int n = pint(st.nextToken());
		int m = pint(st.nextToken());

		//간선 리스트..대신 크기가 주어지니 배열 사용
		edge[] e = new edge[m];
		for (int i = 0; i < m; i++) {
			e[i]=new edge();
			st = new StringTokenizer(br.readLine(), " ");
			
			e[i].n1=pint(st.nextToken());
			e[i].n2=pint(st.nextToken());
			e[i].cost=pint(st.nextToken());
		}
		
		Arrays.sort(e);
		
		init(n);
		
		int cnt=0, i=0;//현재 선택한 edge의 갯수
		int totalCost=0;//누적 전체cost
		while(true) {
			//union이 정상적으로 수행 됬다면
			if(union(e[i].n1, e[i].n2)) {
				//cnt, cost 반영
				cnt++;
				totalCost+=e[i].cost;
			}
			if(cnt==n-1)break;
			i++;
		}
		//최소 cost의 2개의 마을로 분할하는 법 = MST에서 가장 cost가 큰 edge 하나 자르기
		System.out.println(totalCost-e[i].cost);
	}

	//분리집합
	static int[]parents;
	static void init(int n) {
		parents=new int[n+1];
		for (int i = 0; i <= n; i++) {
			parents[i]=i;
		}
	}
	static int find(int n) {
		if(parents[n]==n)return n;
		return parents[n]=find(parents[n]);
	}
	static boolean union(int n1, int n2) {
		int p1=find(n1);
		int p2=find(n2);
		
		if(p1==p2)return false;
		parents[p1]=p2;
		return true;
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
	//간선 객체
	static class edge implements Comparable<edge>{
		int n1;
		int n2;
		int cost;
		@Override
		public int compareTo(edge e) {
			return this.cost-e.cost;
		}		
	}
}

<Prim 사용>

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		int n = pint(st.nextToken());
		int m = pint(st.nextToken());

		ArrayList<ArrayList<int[]>>graph = new ArrayList<>();
		for (int i = 0; i <= n; i++) {
			graph.add(new ArrayList<>());
		}
		//그래프 입력
		for (int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine(), " ");
			int x = pint(st.nextToken());
			int y = pint(st.nextToken());
			int cost = pint(st.nextToken());

			graph.get(x).add(new int[] {y,cost});
			graph.get(y).add(new int[] {x,cost});
		}
		
		int totalCost=0, cnt=0, max=-1;
		//전처리
		boolean[] isV = new boolean[n+1];
		PriorityQueue<int[]>pq = new PriorityQueue<>(new Comparator<int[]>() {
			@Override
			public int compare(int[] o1, int[] o2) {
				// TODO Auto-generated method stub
				return o1[1]-o2[1];
			}
		});
		for (int i = 1; i <= n; i++) {
			pq.offer(new int[] {i,Integer.MAX_VALUE});
		}
		//임의의 시작 노드 설정
		pq.offer(new int[] {1,0});
		
		while(cnt!=n) {
			int[] curNode = pq.poll();

			if(isV[curNode[0]])continue;//이미 방문한 node면
			
			isV[curNode[0]]=true;
			totalCost+=curNode[1];
			if(max<curNode[1])max=curNode[1];
			cnt++;
			
			for (int i = 0; i < graph.get(curNode[0]).size(); i++) {
				int[] next = graph.get(curNode[0]).get(i);
				if(!isV[next[0]]) {
					pq.offer(next);
				}
			}
			
		}
		System.out.println(totalCost-max);
		
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
}

결과 화면(1,2 - Kruskal, 3 - Prim)

[문제링크]

 

1007번: 벡터 매칭

평면 상에 N개의 점이 찍혀있고, 그 점을 집합 P라고 하자. 집합 P의 벡터 매칭은 벡터의 집합인데, 모든 벡터는 집합 P의 한 점에서 시작해서, 또 다른 점에서 끝나는 벡터의 집합이다. 또, P에 속

www.acmicpc.net

 

0. 모든 경우의 수에 대해 계산하는 브루트-포스 방식

 

1. 벡터는 두 점 A, B가 있을때 A-B / B-A 같은 식으로 만들 수 있다

  - 한 점은 +, 한 점은 - 하게 된다

 

2. 즉, 2N개의 점으로 N개의 벡터를 만들었을때 그 벡터들의 합은 N개의 점을 +, 나머지 N개는 - 한 값이다

 

3. 점이 최대 20개인데, 20개 중 10개를 뽑는 조합의 경우의 수 20C10은 184756으로 산술 시간안에 충분히 해결 가능하다

 

4. 10개의 점을 선택해 더하고, 선택되지 않은 10개의 점을 빼는 식으로도 결과를 구할 수 있지만,

  - 산술 연산의 횟수를 줄이기 위해 모든 수를 더해두고, 10개의 점을 선택해 2번씩 빼준다

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		
		int tc = pint(br.readLine());
		for (int test = 1; test <= tc; test++) {
			N = pint(br.readLine());
			isC=new boolean[N];
			point= new int[N][2];
			int sumx=0,sumy=0;
			for (int i = 0; i < N; i++) {
				StringTokenizer st = new StringTokenizer(br.readLine(), " ");
				point[i][0]=pint(st.nextToken());
				point[i][1]= pint(st.nextToken());
				sumx+=point[i][0];
				sumy+=point[i][1];
			}
			
			ans=Double.MAX_VALUE;
			rec(0,0, sumx, sumy);
			sb.append(ans).append("\n");
		}
		System.out.println(sb);
		
	}
	static double ans;
	static int N;
	static boolean[] isC;
	static int[][]point;
	
	static void rec(int cnt, int prev, int x, int y) {
		if(cnt==N/2) {
			ans = Math.min(ans, Math.sqrt((double)x*x + (double)y*y) );
			return;
		}
		for (int i = prev; i < N; i++)
			rec(cnt+1, i+1, x-2*point[i][0], y-2*point[i][1]);
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
}

결과 화면

[게시글링크]

 

글 읽기 - ✅ (수정) solved.ac 1주년 (2021년 6월 5일)

댓글을 작성하려면 로그인해야 합니다.

www.acmicpc.net

 

solved.ac 1주년 기념으로 뱃지 / 프로필 배경을 증정한다고 합니다

 

  • 참여방법 : 이전에 푼 적 없는 문제 1문제 해결
  • 참여기간 : 5/31 ~ 6/7

 

뱃지

 

프로필 배경

 

뱃지와 프로필 배경은 프로필 - 프로필 편집에서 추가/변경 가능합니다

 

적용 모습

 

1. 신장 트리(Spanning Tree)

  • 그래프의 모든 정점을 최소한 적은 간선만 사용해 연결하는 트리를 말한다
  • 정점이 N개라면 N-1개의 간선만 사용해 모든 정점을 연결한 형태를 이루게 된다
  • 신장 트리는 그 구조상 사이클을 포함하지 않는다
  • 만드는 방법 : 한 정점을 기준으로 DFS/BFS를 실행할 때, 탐색한 간선들이 곧 하나의 신장 트리가 된다

 

 

2. 최소 신장 트리(Minimum Spanning Tree, MST)

  • 최소 신장 트리란, 가중치 있는 그래프에서, 신장트리를 이루는 간선의 가중치 총합이 최소가 되는 신장트리를 말한다
  • 모든 정점들을 최소 비용으로 연결하는 것

 

 

3. 최소 신장 트리 구현

  • 크루스칼(KRUSKAL) 알고리즘 (간선 기반)
    https://visualgo.net/en/mst
     
    • Greedy하게 간선을 선택하여 최소 신장 트리를 완성한다
    • 간선의 가중치가 작을수록 선택에 우선순위를 두되,
    • 해당 간선을 선택함으로 인해 트리에 사이클이 발생한다면, 선택하지 않고 넘어간다
    • n-1개의 간선을 선택하면, MST가 완성된 것이다
    • 사이클의 발생 여부 탐색 : 간선의 양쪽 정점 사이에 이미 경로가 존재한다면, 사이클이 발생하게 된다
    • 분리집합을 이용해 간선 선택시 양 정점의 집합을 합쳐, 사이클 발생을 막을 수 있다

 

  • 프림(PRIM) 알고리즘 (정점 기반)
    https://visualgo.net/en/mst
    • 한 정점에서 시작해 최적의 간선만을 선택해 트리를 확장해 나간다
    • 현재 트리에 속한 정점들의 인접 정점 중, 가장 적은 비용으로 진행할 수 있는 정점을 선택, 트리에 포함시킨다
    • n-1개의 정점을 선택하면, MST가 완성된 것이다
    • 다익스트라와 형태가 거의 비슷하지만, 다익스트라는 특정 노드를 기준으로 누적된 거리를 기준으로 한다

 

 

4. JAVA 기초 코드(문제링크)

  • Kruskal 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		int n = pint(st.nextToken());
		int m = pint(st.nextToken());

		//간선 리스트..대신 크기가 주어지니 간선배열 사용
		edge[] e = new edge[m];
		for (int i = 0; i < m; i++) {
			e[i]=new edge();
			st = new StringTokenizer(br.readLine(), " ");
			
			e[i].n1=pint(st.nextToken());
			e[i].n2=pint(st.nextToken());
			e[i].cost=pint(st.nextToken());
		}
		//정렬, 작은 간선부터 확인
		Arrays.sort(e);
		//분리집합 초기화
		init(n);
		
		int cnt=0, i=0;//현재 선택한 edge의 갯수, 현재 edge index
		int totalCost=0;//전체cost
		while(true) {
			//union이 정상적으로 수행 됬다면 ( = cycle을 이루지 않는 간선이 tree에 포함되었다면)
			if(union(e[i].n1, e[i].n2)) {
				//cnt, cost 반영
				cnt++;
				totalCost+=e[i].cost;
			}
			if(cnt==n-1)break;//n-1개의 간선을 선택했다면 MST의 완성
			i++;
		}
		//최소 cost의 2개의 마을로 분할하는 법 = MST에서 가장 cost가 큰 edge 하나 자르기
		System.out.println(totalCost-e[i].cost);
	}

	//분리집합
	static int[]parents;
	static void init(int n) {
		parents=new int[n+1];
		for (int i = 0; i <= n; i++) {
			parents[i]=i;
		}
	}
	static int find(int n) {
		if(parents[n]==n)return n;
		return parents[n]=find(parents[n]);
	}
	static boolean union(int n1, int n2) {
		int p1=find(n1);
		int p2=find(n2);
		
		if(p1==p2)return false;
		parents[p1]=p2;
		return true;
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
	//간선 객체
	static class edge implements Comparable<edge>{
		int n1;
		int n2;
		int cost;
		@Override
		public int compareTo(edge e) {
			return this.cost-e.cost;
		}		
	}
}

 

  • Prim 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		int n = pint(st.nextToken());
		int m = pint(st.nextToken());

		ArrayList<ArrayList<int[]>>graph = new ArrayList<>();
		for (int i = 0; i <= n; i++) {
			graph.add(new ArrayList<>());
		}
		
		//그래프 입력
		for (int i = 0; i < m; i++) {
			st = new StringTokenizer(br.readLine(), " ");
			int x = pint(st.nextToken());
			int y = pint(st.nextToken());
			int cost = pint(st.nextToken());

			graph.get(x).add(new int[] {y,cost});
			graph.get(y).add(new int[] {x,cost});
		}
		
		int totalCost=0, cnt=0, max=-1;
		//전처리
		boolean[] isV = new boolean[n+1];
		PriorityQueue<int[]>pq = new PriorityQueue<>(new Comparator<int[]>() {
			@Override
			public int compare(int[] o1, int[] o2) {
				// TODO Auto-generated method stub
				return o1[1]-o2[1];
			}
		});
		for (int i = 1; i <= n; i++) {
			pq.offer(new int[] {i,Integer.MAX_VALUE});
		}
		//임의의 시작 노드 설정
		pq.offer(new int[] {1,0});
		
		while(cnt!=n) {//n개의 정점을 선택하면 MST의 완성
			int[] curNode = pq.poll();

			if(isV[curNode[0]])continue;//이미 방문한 node면 무시한다
			
			isV[curNode[0]]=true;
			totalCost+=curNode[1];
			if(max<curNode[1])max=curNode[1];//MST의 가장 큰 edge 저장
			cnt++;
			
			for (int i = 0; i < graph.get(curNode[0]).size(); i++) {
				int[] next = graph.get(curNode[0]).get(i);
                if(!isV[next[0]]) {
					pq.offer(next);
				}
			}
			
		}
		System.out.println(totalCost-max);
		
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
}

 

https://www.tutorialspoint.com/data_structures_algorithms/graph_data_structure.htm

1. Graph 자료구조

  • Graph란, 정점(node, vertex)과 정점 사이를 잇는 간선(edge, link)으로 구성된 자료구조이다
  • 객체와 객체 사이의 관계를 나타내는데 좋은 자료구조로, 지하철 노선도, 비행기의 항로, 도로 등 다양하게 응용 가능하다

 

2. Graph의 구성

  • 정점 : Graph에 속한 객체 (노선도의 경우 각 역)
  • 간선 : 정점 사이의 관계를 나타내는 선 (두 역 사이에 지하철이 다니면 선이 존재하지만, 다니지 않으면 존재하지 않는다)
  • 차수 : 정점에 연결된 간선의 갯수 (신촌역은 차수가 2이지만, 왕십리역은 차수가 6이다)
  • 입력 차수(진입 차수) : 방향 그래프의 경우, 정점으로 들어오는 간선의 갯수
  • 출력 차수(진출 차수) : 방향 그래프의 경우, 정점에서 나가는 간선의 갯수
  • 사이클 : 어떠한 정점 A에서 간선을 한번씩만 이동했을때, 자기 자신으로 돌아오게 되는 경로, 또는 그러한 그래프
  • 인접 정점 : 어떤 정점 A에서 간선으로 직접 연결된 정점

 

3. Graph의 종류

무방향그래프 / 방향그래프

  • 무방향그래프
    https://mathinsight.org/definition/undirected_graph
    • 간선에 진행 방향이 없는 그래프, 양 방향 이동이 자유롭다
    • A->B 간선은 B->A간선과 동일하다
    • ex) 지하철

 

  • 방향그래프
    https://mathinsight.org/definition/directed_graph
    • 간선에 진행 방향이 있는 그래프, 해당 방향으로만 진행 가능
    • A->B간선만 존재하면, B에서 A로는 이동할 수 없다
    • ex) 일방통행 도로

 

완전그래프 / 부분그래프

  • 완전그래프 
    https://en.wikipedia.org/wiki/Complete_graph
    • 모든 정점 사이에 간선이 연결된 그래프
    • 간선의 갯수는 n(n-1)/2이다 (무방향 기준)

 

  • 부분그래프
    • 완전그래프가 아닌 그래프
    • 간선으로 직접 연결되지 않은 정점들이 존재한다

 

가중치그래프

  • 가중치그래프
    http://www.mathcs.emory.edu/~cheung/Courses/171/Syllabus/11-Graph/weighted.html
    • 간선에 각각 가중치 value가 부여된 그래프
    • ex) 지하철의 요금, 도로의 거리 등

 

4. Graph의 저장 방법

인접 행렬 / 인접 리스트

  • 인접 행렬
    • N개의 정점이 있다면, N*N 2차원 배열으로 연결 정보를 관리한다
    • adj[i][j]는 i->j로 가는 간선의 유/무 혹은 그 갯수를 저장한다
    • 장점 : 특정 노드들 사이의 간선 존재 여부를 O(1)으로 확인 가능하다
    • 간선의 갯수가 많은 밀집 그래프에서 유리하다
    • 단점 : O(N^2) 크기의 배열이 필요하다
  • 인접 리스트
    • 각 정점에 대해 리스트를 만들어, 인접한 정점정보만 저장한다
    • 간선이 없는 정점은 저장하지 않는다
    • 장점 : 실제 간선이 존재하는 경우만 저장하므로, 공간 복잡도가 낮아진다
    • 간선의 갯수가 적은 희소 그래프에서 유리하다
    • 단점 : 특정 노드들 사이의 간선 존재 여부를 확인하려면, 리스트를 탐색해야한다

간선 리스트

  • 간선 리스트
    • 정점이 아닌, 모든 간선의 리스트를 저장한다

 

5. Graph 연관 알고리즘

Graph 탐색 (링크)

  • DFS
  • BFS

최소신장트리(Minimum Spanning Tree, MST) (링크)

  • 그래프의 모든 정점을 잇는 간선의 가중치의 합이 최소가 되는 트리
  • 크루스칼(KRUSKAL) 알고리즘
  • 프림(PRIM) 알고리즘

최단경로 탐색

  • 특정 정점에서 다른 정점들까지 가는 최소 가중치의 경로를 탐색
  • 다익스트라(Dijkstra) 알고리즘
  • 벨만-포드 알고리즘
  • 플로이드-와샬 알고리즘

+ Recent posts