no image
[OS2] Multi programming, Multi Tasking
전기창지 CPU와 기계장치인 Input/Output device간 속도 차이(병목 현상)으로 인해 I/O 디바이스가 작업을 하는 동안 task가 Blocked되는 현상이 존재했다. 레지스터 -> 캐시 -> 메인 메모리(RAM) -> Disk(SSD, HDD) 우측으로 갈수록 각 메모리 접근 시간이 점점 느려진다. 특히나 캐시 메모리는 메인 메모리와 레지스터 간 속도 차이를 줄이기 위해 존재한다. Spooling 주변기기와 컴퓨터 처리 장치 간에 데이터를 전송할때 처리 지연을 단축하기 위해 보조기억 장치를 완충 기억 장치로 사용하는 것 중앙 처리장치와 I/O 디바이스 등 컴퓨터 주변 장치의 독립적으로 작동하게 한다. 보조 기억장치를 하나의 버퍼로 사용한다고 볼 수 있다. I/O요청이 있으면 디바이스와 연..
2024.02.21
no image
[OS1] Abstraction, Policy
*숭실대학교 소프트웨어 학부 운영체제 과목을 참고하여 작성하였습니다* Operationg System(OS) 운영체제란 컴퓨터 소프트웨어와 하드웨어 간 인터페이스 하드웨어 자원을 쉽고 효율적으로 사용할 수 있도록 Abstraction을 제공하는 시스템 소프트웨어 Abstraction 추상화란 복잡한 개념 및 시스템을 단순화 하는 것이다. 운영체제는 컴퓨터의 물리 자원을 쉽게 사용할 수 있도록(system call) 추상화를 제공한다. CPU -> Process Memory -> Address Space(Virtual Address Space) Disk -> File 하드웨어의 동작과 자원을 보다 포괄적이고 직관적인 개념으로 추상화 시킨 예시들을 볼 수 있다. Policy 운영체제는 PC뿐 아니라 서버, ..
2024.02.20
no image
프로그래머스 여행경로 자바스크립트
어렵게 접근해서 조금 헤맸다. 일반적인 dfs 경로 탐색과 유사하지만 '알파벳 순이 빠른 경로' 라는 조건때문에 어렵게 생각했다. 프로그래머스 문제는 명쾌하게 풀리는 편이라고 생각한다. 그러므로 너무 복잡하게 접근하지 않는 편이 좋을 것 같다. 중복 경로(같은 티켓이 또 들어오는 경우)인 케이스와 첫번째 정렬 경로로 모두 방문할 수 없는 경우 이 2가지를 고려하는 게 핵심이다. function solution(tickets) { var answer = []; const map = []; const visited = []; for(let i = 0; i < tickets.length; i++){ if(map[tickets[i][0]] === undefined){ map[tickets[i][0]] = [];..
2024.02.20
no image
구름톤 챌린지 3주 차 문제 14 학습 일기
https://goor.me/wDFSjuDNjzXTTqH4A ​ import java.io.*; import java.util.*; 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 = Integer.parseInt(st.nextToken()); int M = Integer.parseInt(st.nextToken()); int K = Integer.parseInt(st.nextToken(..
2024.02.18
no image
구름톤 챌린지 4주 차 문제 18 학습 일기
https://goor.me/wDFSjuDNjzXTTqH4A ​ import java.io.*; import java.util.*; 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 = Integer.parseInt(st.nextToken()); int m = Integer.parseInt(st.nextToken()); Pair[][] arr = new Pair[n + 1][n + 1];..
2024.02.18
no image
구름톤 챌린지 3주 차 문제 13 학습 일기
https://goor.me/wDFSjuDNjzXTTqH4A ​ import java.io.*; import java.util.*; 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 = Integer.parseInt(st.nextToken()); int K = Integer.parseInt(st.nextToken()); int [][]map = new int[N][N]; int []ans..
2024.02.18
no image
구름톤 챌린지 4주 차 문제 17 학습 일기
https://goor.me/wDFSjuDNjzXTTqH4A ​ import java.io.*; import java.util.*; 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 = Integer.parseInt(st.nextToken()); int M = Integer.parseInt(st.nextToken()); ArrayList arr = new ArrayList(); for(i..
2024.02.18
no image
백준 1436 영화감독 숌
어느 영화감독이 시리즈를 제작하는데, 흔하게 1,2편으로 표기하는 것이 싫다고 666이 들어간 자연수 순서대로 시리즈를 제작한다는 스토리이다. 초등학교 저학년 때 어디선가 보았던 '책상은 책상이다' 라는 단편이 기억난다. 주인공은 삶이 너무 지루하다며, 침대를 의자라고 부르고 의자를 시계라고 부르는 힙스터가 되버린다. 이런 기행을 계속하던 주인공이 원래 사물의 이름을 까먹게 되고 타인과 대화가 불가능한 수준에 다다르는 내용까진 기억이 나는데, 후에 잘못을 뉘우치고 다시 광명을 찾는지는 모르겠다. 만약 아니라면 초등학교 저학년에게는 꽤나 비극적인 내용이 아닐 수 없겠다. 아무튼 이 영화감독이나 책상은 책상이다의 주인공이나, 창의성 점수는 높게 주지만 적당히 할 필요가 있다. 나 또한 반골기질이 강한 사람이..
2024.02.18

 

 

 

전기창지 CPU와 기계장치인 Input/Output device간 속도 차이(병목 현상)으로 인해

I/O 디바이스가 작업을 하는 동안 task가 Blocked되는 현상이 존재했다.

 

레지스터 -> 캐시 -> 메인 메모리(RAM) -> Disk(SSD, HDD)

우측으로 갈수록 각 메모리 접근 시간이 점점 느려진다.

특히나 캐시 메모리는 메인 메모리와 레지스터 간 속도 차이를 줄이기 위해 존재한다.

 

 

Spooling

주변기기와 컴퓨터 처리 장치 간에 데이터를 전송할때 처리 지연을 단축하기 위해 보조기억 장치를 완충 기억 장치로 사용하는 것

중앙 처리장치와 I/O 디바이스 등 컴퓨터 주변 장치의 독립적으로 작동하게 한다.

보조 기억장치를 하나의 버퍼로 사용한다고 볼 수 있다.

I/O요청이 있으면 디바이스와 연결된 저장장치 공간에 task를 쌓아놓고 cpu는 다른 작업을 할 수 있다.

이는 프로세스 입장에서는 입출력 처리가 발생할 시 이전 작업의 종료를 기다리지 않고 I/O 디바이스 버퍼에 요청을 쌓아놓으면 되는 것이기 때문에, 프로세스당 독립적인 입출력 장치를 할당받는 것으로 이해할 수도 있다.

 

 

Multi Programming

초기 컴퓨터는 하나의 task를 실행하고 완료된 후 다음 task를 실행할 수 있었다 즉 한번에 하나의 작업만 가능했다.

하지만 위에서 말했듯 입출력 기기와 프로세서 간 속도 차이와 각 메모리의 접근 시간 차이가 있기 때문에 병목현상이 자주 발생했다.

사람은 비싼 돈 주고 산 기계가 쉬는 꼴을 못보기 마련이다. 이런 비효율을 해결고자, 컴퓨터를 어떻게든 혹사시키기 위해 프로세서가 입출력 작업을 요청하고 대기시간 동안 다른 일을 처리할 수 있도록 하는 것이 MultiProgramming이다

 

 

그림의 예시로 보면

만약 Job 1이 I/O 처리를 위한 대기 시간으로 들어가면 다음 Job2가 실행되는 것이다.

Job2는 I/O 요청이 없다면 끝까지 처리한다. 이후 또 다음 스케쥴된 Job으로 넘어가는 것이다.

 

근데, 딱봐도 조금 이상해 보이나. 일단 다음 job이 수행되려면 현재 수행되는 job이 I/O를 해야 할것이고, Job1이 I/O요청이 있는 간단한 작업이고 Job2는 I/O작업이 없는 복잡한 작업이라면 Job1은 I/O처리를 끝내고도 계속 다음 스케쥴을 기다려야 한다.

 

 

 

Time sharing

CPU의 실행 시간을 타임 슬라이스 (Time Slice)로 나누어 실행한다.

모든 프로그램이 타임 슬라이스 동안 CPU를 점유하고 그 시간이 끝나면 CPU를 양보한다.

나중에 얘기할 Round Robin 과 형태가 유사하다고 할 수 있다.

여러개의 작업들이 cpu 스위칭을 통해 동시에 실행된다.

그야말로 공산주의 식으로 매우 공평하긴 하지만, 일단 cpu 스위칭, Context Switching 이 빈번하기 때문에 오버헤드가 커질 수 밖에 없고, 많은 작업이 필요한 job과 간단한 job 모두 동일한 작업 시간을 할당받기 때문에 비효율을 야기할 수 있다.

 

Multitasking

여러개의 Task emfdl CPUR와 같은 자원을 공유하도록 하는 방법

대표적으로 한개의 Job에 Unix fork() 시스템콜을 통해 여러개의 프로세스로 실행시키는 것이 있다.

즉 여러 프로그램이 동시에 수행(Concurrent Execution) 된다고 볼 수 있다.

멀티 태스킹 이슈

- 메모리 관리

   - 동시에 여러 프로그램이 메모리에 상주하기 때문에 각 프로세스 간 접근하면 안되는 메모리를 보호하는 시스템이 필요하다.

- cpu scheduling

    - cpu 스케쥴링 Policy가 필요하다

- 동기화

   - 공유 자원에 동시 접근에 대해서 접근 순서를 보장해줄 필요가 있다.

 

  

 

그 외 시스템

*숭실대학교 소프트웨어 학부 운영체제 과목을 참고하여 작성하였습니다*

Operationg System(OS)

운영체제란 컴퓨터 소프트웨어와 하드웨어 간 인터페이스

하드웨어 자원을 쉽고 효율적으로 사용할 수 있도록 Abstraction을 제공하는 시스템 소프트웨어

Abstraction

추상화란 복잡한 개념 및 시스템을 단순화 하는 것이다.

운영체제는 컴퓨터의 물리 자원을 쉽게 사용할 수 있도록(system call) 추상화를 제공한다.

 

CPU -> Process

 

Memory -> Address Space(Virtual Address Space)

Disk -> File

하드웨어의 동작과 자원을 보다 포괄적이고 직관적인 개념으로 추상화 시킨 예시들을 볼 수 있다.

 

Policy

운영체제는 PC뿐 아니라 서버, 스마트폰, 자동차까지 다양한 영역에 사용된다.

운영체제는 각 시스템에서 고려되는 우선 사항에 맞추어, 한정된 컴퓨팅 자원, 리소스를 효율적으로 사용할 수 있어야 한다.

그에 맞추어 알맞은 정책(Policy)을 결정해서 사용한다.

 

대표적으로

FIFO(First In First Out)

LRU(Least Recently Used)

RR(Round Robin)

등등

 

 

 

 

 

 

 

 

 

어렵게 접근해서 조금 헤맸다.

일반적인 dfs 경로 탐색과 유사하지만 '알파벳 순이 빠른 경로' 라는 조건때문에 어렵게 생각했다.

프로그래머스 문제는 명쾌하게 풀리는 편이라고 생각한다. 그러므로 너무 복잡하게 접근하지 않는 편이 좋을 것 같다.

중복 경로(같은 티켓이 또 들어오는 경우)인 케이스와 첫번째 정렬 경로로 모두 방문할 수 없는 경우

이 2가지를 고려하는 게 핵심이다.

function solution(tickets) {
    
    var answer = [];
    
    const map = [];
    const visited = [];
    
    for(let i = 0; i < tickets.length; i++){
        if(map[tickets[i][0]] === undefined){
            map[tickets[i][0]] = [];
            visited[tickets[i][0]] = [];
        }
        map[tickets[i][0]].push(tickets[i][1]);
        visited[tickets[i][0]].push(0);
    }
    
    const dfs = (air, br, k) => {
        
        
        if(k === tickets.length + 1 && answer.length === 0) {
            answer = [...br];
            return;
        }
        
        if(answer.length > 0)
            return;

        const arr = map[air];
        if(arr === undefined)
            return;
        
        arr.sort();
        for(let i = 0; i < arr.length; i++){
            if(visited[air][i] === 0){
                visited[air][i] = 1;
                br[k] = arr[i];
                dfs(arr[i], br, k+1);
                visited[air][i] = 0;
            }
        }
    }

 

같은 티켓이 또 들어오는 경우가 있기 때문에,

방문때마다 visited를 일반적인 공항이름을 넣거나 하면 같은 티켓을 탐색할 수 없다.

때문에 visited를 map과 동일한 배열을 만들어서 방문 체크를 진행했다.

한 경로에 탐색이 끝나고 해당 경로로 모든 경로를 방문할 수 없을 때를 위해

탐색이 끝나면 visited를 다시 방문하지 않은 것으로 되돌린다.

 

import java.io.*;
import java.util.*;
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 = Integer.parseInt(st.nextToken());
		int M = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		
		ArrayList<ArrayList<Integer>> arr = new ArrayList<>();
		for(int i =0; i <= N; i++){
			arr.add(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());
			arr.get(a).add(b);
			arr.get(b).add(a);
		}
		PriorityQueue<Integer> pq = new PriorityQueue<>();
		boolean []visited = new boolean[N+1];
		
		for(int i = 0; i < arr.get(K).size(); i++){
			pq.add(arr.get(K).get(i));
		}
		
		visited[K] = true;
		int ans = K;
		int cnt = 1;
		while (!pq.isEmpty()){
			int node = pq.poll();
			if(visited[node] == false){
				ans = node;
				cnt++;
				visited[node] = true;
				pq = new PriorityQueue<Integer>();
				for(int i = 0; i < arr.get(node).size(); i++){
						pq.add(arr.get(node).get(i));
				}
			}
		}
		System.out.print(cnt);
		System.out.println(" " + ans);
	}
}

 

행렬 탐색 문제이다. 본인 또한 대부분 사람들과 비슷하게 그래프 탐색 원툴이기 때문에 그리디, dp같은건 잘 못푼다. 그래서 탐색 위주로 풀었다.

시작 노드로부터 인접 노드로 방문하는데

  1. 한번 방문한 노드는 방문할 수 없고
  2. 방문할 수 있는 노드중에 번호가 가장 작은 노드로 이동한다.

번호가 작은 노드로 이동한다는 규칙에서 우선 순위 큐 를 활용하는 아이디어를 바로 낼 수 있다.

물론 구름 측에서 제시한 코드를 보니 일반 큐에 넣은 후 정렬을 이용함. 아무거나 해도 상관 없을 것이다.

다만 우선순위 큐를 활용한다면, 매번 방문 후 다음에 방문할 노드를 큐에 넣을 때, 큐를 초기화해줘야 하는데, 이를 위해서 new를 사용하면 메모리 할당이 많아져 속도가 느려질 수도 있겠다. 근데 난 그냥 했다.

그리고 그래프에서 간선의 존재 유무를 굳이 확인할 필요가 없다면 인접행렬보단 인접 리스트로 구현하는 것이 공간 복잡도와 탐색 속도 면에서 낫기 때문에 arraylist를 이용한 인접 리스트를 선택한다.

 

 

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

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

        Pair[][] arr = new Pair[n + 1][n + 1];
        for(int i=0; i<=n; i++){
            for(int j=0; j<=n; j++){
                arr[i][j] = new Pair();
            }
        }

        for(int i=0; i<m; i++){
            st = new StringTokenizer(br.readLine());
            int y = Integer.parseInt(st.nextToken());
            int x = Integer.parseInt(st.nextToken());
            char c= st.nextToken().charAt(0);
            
            if(c == 'R'){
                for(int k=x;k<=n;k++)
                    arr[y][k].x += 1;
                
            } else if(c == 'L'){
                for(int k=1;k<=x;k++)
                    arr[y][k].x += 1;
                
            } else if(c == 'D'){
                for(int k=y;k<=n;k++)
                    arr[k][x].y += 1;
                
            } else if(c == 'U'){
                for(int k=1;k<=y;k++)
                    arr[k][x].y += 1;
             }
         }

         long ans=0;

         for(int i=1;i<=n;i++){
             for(int j=1;j<=n;j++){
                 ans+=arr[i][j].x*arr[i][j].y;
             } 
         }

         System.out.println(ans);
     }

     static class Pair{
         long y;
         long x;

         public Pair(){
             this.y=0;
             this.x=0;
          } 
      }
}

 

이 문제는 꽤 재밌었다.

탐색 + dp 문제

세로선 가로선이 교차하는 점의 개수를 구하면 되는데, 가로선끼리 세로선끼리는 겹치지 않는다.

그래서 2차원 배열로 맵을 만들고 각 칸마다 세로선과 가로선의 개수를 기록해준다.

이후에 세로선 개수 X 가로선 개수 를 누적합하면 정답이 나온다.

아까 공개된 정답 코드 봤는데 완벽하게 이해하지는 못했고 아마 비슷한 접근인 듯 한데, 정답 코드는 굳이 3차원 배열 써서 조건문 돌 때 마다 누적합까지 해줬고

나는 pair를 2차원 배열에 저장해서 세로선과 가로선을 기록했다.

이렇게 해서 네이버 페이 10000원을 벌었다.

 

 

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

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 = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		
		int [][]map = new int[N][N];
		int []ans = new int [32];
		
		Queue<Node> q = new LinkedList<>();
			
		int []dx = {-1, 1, 0, 0};
		int []dy = {0, 0, -1, 1};
		
		for(int i = 0; i <N; i++){
			st = new StringTokenizer(br.readLine());
			for(int j = 0; j < N; j++){
				map[i][j] = Integer.parseInt(st.nextToken());
			}
		}
		
		int max = 0;
		int res = 0;
		
		for(int i = 0; i <N; i++){
			for(int j = 0; j < N; j++){
				int cnt = 0;
				if(map[i][j] != 0){
					cnt++;
					q.add(new Node(i, j));
					int num = map[i][j];
					map[i][j] = 0;
					while(!q.isEmpty()){
						Node node = q.poll();
						for(int k = 0; k <4; k++){
							int newi = node.i + dx[k];
							int newj = node.j + dy[k];
							if(newi >=0 && newi < N && newj >=0 && newj < N && map[newi][newj] == num){
								cnt++;
								q.add(new Node(newi, newj));
								map[newi][newj] = 0;
							}
						}
					}
					if (cnt >= K){
						ans[num] ++;
					}
				}
			}
		}
		for(int i =0; i<32; i++){
			if (ans[i] >= max){
				max = ans[i];
				res = i;
			}
		}
		System.out.println(res);
	}
}

class Node{
	int i;
	int j;
	
	Node(int i, int j){
		this.i = i;
		this.j = j;
	}
}

탐색 문제이다. bfs로 풀었다.

백준 10026 적록색약 문제와 유형이 비슷하다.

같은 정수가 상하 좌우로 k개 이상 인접해있다면 1개의 단지로 볼 수 있는데, 단지가 가장 많은 정수를 출력하면 된다.

같은 수가 3개 이상 인접해있다면 단지로 볼 수 있다면

1 1 1 2

2 2 1 3

1 1 3 3

이 예시에서는 1단지 1개 2단지 0개 3단지 1개 이다.

탐색을 통해 인접 행렬을 탐색하고 같은 수가 k개 이상 붙어있다면 단지의 수를 1개 늘려주면 된다.

주의해야할 점이 있다면 단지의 갯수만 따져야한다. 단지 + 숫자의 갯수까지 따지면 안된다.

예를 들어

1 1 1 1 1

1 1 1 1 3

1 1 1 3 3

3 3 3 1 1

이런 예시가 있다면 1단지 1개 3단지 2개로 3을 출력해야 한다. 1이 많다고 출력하면 안된다.

그리고 이거 리뷰 쓰면 네이버 포인트 5000원 준다.

 

import java.io.*;
import java.util.*;
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 = Integer.parseInt(st.nextToken());
		int M = Integer.parseInt(st.nextToken());
		
		ArrayList<ArrayList<Integer>> arr = new ArrayList<>();
		for(int i =0; i <= N; i++){
			arr.add(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());
			arr.get(a).add(b);
			arr.get(b).add(a);
		}
		Queue<Integer> q = new LinkedList<>();
		boolean []visited = new boolean[N+1];
		ArrayList<Node> ans = new ArrayList<>();
		
	
		for(int i = 0; i < N; i++){
			Double edge = 0.0;
			Node nd = new Node();
			if(arr.get(i).size() > 0 && !visited[i]){
				visited[i] = true;
				nd.nodeNum.add(i);
				for(int j = 0; j < arr.get(i).size(); j++){
					q.add(arr.get(i).get(j));
				}
				while (!q.isEmpty()){
					int nextNode = q.poll();
					edge++;
					if(!visited[nextNode]){
						visited[nextNode] = true;
						nd.nodeNum.add(nextNode);
						for(int j = 0; j < arr.get(nextNode).size(); j++){
							q.add(arr.get(nextNode).get(j));
						}
					}
				}
			nd.ans = edge / nd.nodeNum.size();
			ans.add(nd);
			}
		}
	
		for(int i = 0; i < ans.size(); i++)
			Collections.sort(ans.get(i).nodeNum);
		Collections.sort(ans, Node.customComparator);
		System.out.println(ans.get(0).ans);
		
		for(int i = 0; i <ans.get(0).nodeNum.size(); i++)
			System.out.print(ans.get(0).nodeNum.get(i) + " ");
	}
}


class Node{
    ArrayList<Integer> nodeNum = new ArrayList<>();
    Double ans;
	
    public static Comparator<Node> customComparator = new Comparator<Node>() {
        @Override
        public int compare(Node node1, Node node2) {
            // 1. ans가 가장 높은 것
            int ansComparison = Double.compare(node2.ans, node1.ans);
            if (ansComparison != 0) {
                return ansComparison;
            }

            // 2. ans가 같을 때 nodeNum의 인자갯수가 적은 것
            int numCountComparison = Integer.compare(node1.nodeNum.size(), node2.nodeNum.size());
            if (numCountComparison != 0) {
                return numCountComparison;
            }
					
            // 3. 2까지 같을 때는 nodeNum의 인자 중 가장 작은 인자가 있는 Node
            for (int i = 0; i < node1.nodeNum.size(); i++) {
                int numComparison = Integer.compare(node1.nodeNum.get(i), node2.nodeNum.get(i));
                if (numComparison != 0) {
                    return numComparison;
                }
            }

            return 0; // 모든 비교 기준이 같을 경우
        }
    };
}

 

쉽게 말하면 노드와 간선 수를 둘 다 구하면 된다.

근데 약간 꼼수 아닌 꼼수가 있다면, 실제는 양방향 간선으로 양 노드 간 간선 수를 하나로 세야 하지만,

그냥 각각 노드에서 간선의 수를 카운트 했기 때문에 2개로 치고 풀었다.

그래서 실제 밀도를 구하면 예시와는 다르지만 답은 맞는다.

그리고 밀도가 같을 때가 참 지저분한데, 그건 우리 형 gpt의 도움을 받아서 comparator의 compare 메소드를 오버라이드 해서 구현했다.

제시한 정답에서는 간선의 개수를 따로 세었는데. 난 그냥 탐색할 때 카운트 했다.

어느 영화감독이 시리즈를 제작하는데, 흔하게 1,2편으로 표기하는 것이 싫다고 666이 들어간 자연수 순서대로 시리즈를 제작한다는 스토리이다.
초등학교 저학년 때 어디선가 보았던 '책상은 책상이다' 라는 단편이 기억난다. 주인공은 삶이 너무 지루하다며, 침대를 의자라고 부르고 의자를 시계라고 부르는 힙스터가 되버린다. 이런 기행을 계속하던 주인공이 원래 사물의 이름을 까먹게 되고 타인과 대화가 불가능한 수준에 다다르는 내용까진 기억이 나는데, 후에 잘못을 뉘우치고 다시 광명을 찾는지는 모르겠다. 만약 아니라면 초등학교 저학년에게는 꽤나 비극적인 내용이 아닐 수 없겠다. 아무튼 이 영화감독이나 책상은 책상이다의 주인공이나, 창의성 점수는 높게 주지만 적당히 할 필요가 있다. 나 또한 반골기질이 강한 사람이지만, 기본적인 사회적 합의는 지키면서 사는 것이 좋겠다.
아무튼 이 영화감독이 정한 시리즈의 규칙은
1편 = 666
2편 = 1666
3편 = 2666
.
.
.
7편 = 6660
8편 = 6661
이런식이다
최적화 방법은 모르겠고, 카테고리도 '부르트 포스' 에 있기 때문에, 무식하고 미련한 방식으로 해결한다. 사실 일반적인 알고리즘 문제에서 이중 반복문 미만은 최적화에 크게 포커스를 둘 필요도 없을 것이다. 숫자를 666부터 1씩 더해가면서 '666'이 들어있으면 카운트를 해준다. 카운트와 입력받은 숫자가 일치하면 출력해주면 된다.
처음에는 int를 유지하면서 해결하려고 했다. 10으로 나눈 나머지가 숫자 6이면 수를 한번 세주고 아니면 0으로 초기화 해준다. 이렇게 하면 연속적으로 6이 있어야만 0으로 초기화 되지 않는데, 3까지 도달하면 그 수는 '666'이 포함됐다고 할 수 있겠다. 그렇게 제출해서 한번 틀리고 문자열로 치환해서 검사하면 훨씬 편할 것 같다는 생각을 했다.
1씩 더해주며 검사하는 것은 똑같지만, 문자열로 바꾸니 인덱스를 돌면 되니 훨씬 편했다. 인덱스를 돌다가 일단 '6'이 나오면, 이후에 두번 다 6이 나오는 지 체크했다.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int beast_num_checker(char *beast_num)
{
    int i;

    i = 0;
    while (beast_num[i])
    {
        if (beast_num[i] && beast_num[i] == '6')
        {
            if (beast_num[i + 1] && beast_num[i+1] == '6')
            {
                if (beast_num[i+2] && beast_num[i+2] == '6')
                    return (1);
            }
        }
        i++;
    }
    return (0);
}


char *itoa(int a)
{
    int num;
    int cnt;
    char *itoa_num;

    num = a;
    cnt = 0;
    while (num != 0)
    {
        num /= 10;
        cnt++;
    }

    itoa_num = malloc(cnt + 1);
    itoa_num[cnt] = '0';
    cnt--;

    while (cnt >= 0)
    {
        itoa_num[cnt] = (a % 10) + '0';
        a /= 10;
        cnt--;
    }
    return (itoa_num);
}


int main()
{
    int a;
    int num;
    int cnt;
    int beast_num;
    
    beast_num = 666;
    cnt = 0;
    num = 0;
    while (1)
    {
        a = getc(stdin);
        if (a == 'n')
            break;
        num = num * 10 + (a - '0');
    }

    while (1)
    {
        if (beast_num_checker(itoa(beast_num)))
        {
            cnt++;
            if (cnt == num)
                break;
        }
        beast_num++;
    }
    printf("%d", beast_num);
}
구현은 위에 설명한 것이 다이고. 오히려 특이사항이 있다면, 최대한 로우레벨로 구현하기 위해 scanf를 사용하지 않은 것과 itoa 부분이다.
getc를 통해서 표준입력으로 부터 한글자씩 입력 받았다. getc함수는 문자의 아스키 코드를 int형으로 반환해주기 때문에, 1 은 49 2는 50으로 반환되는 식이다. 그래서 반환값에서 문자 '0' 즉 48을 빼줘야 진짜 int형으로 저장이 된다. 그리고 숫자를 입력 받을 때 마다 이전 숫자에 10을 곱한 후 더해주어서 10진수의 자릿수 연산을 해주었다. getc를 통해 읽어온 문자가 개행문자일 때 반복문 탈출 조건을 줬기 때문에, enter를 누르면 입력이 종료된다.
itoa 부분도 비슷한데, getc를 통해 받은 값을 0이 될때까지 10으로 나누어 보면서 자릿수를 체크하고 malloc을 통해 자릿수 +1 만큼 메모리를 할당해준다. 맨 마지막 인덱스에는 null문자를 넣어주고, 10의 나머지 연산을 통해 숫자를 문자열에 거꾸로 넣어준다.
num = 123 으로 치면 123을 10으로 나눈 나머지 3을 넣어주고, num에는 123 을 10으로 나눈 12로 초기화 해준다. 다시 12를 10으로 나눈 나머지 2를 넣어주고 앞의 과정을 반복해주면 된다. 나머지 연산을 활용하기 때문에 거꾸로 넣어줘야 한다.
문자열에 넣어줄 때는 아까 getc와는 반대로, 문자 '0' 만큼의 값을 더해줘서 문자열에 넣어줘야 숫자가 의미하는 문자로서 문자열에 넣어줄 수 있다.