728x90

상어 초등학교  

1 초 (추가 시간 없음) 1024 MB 20256 8764 6014 40.682%

문제

상어 초등학교에는 교실이 하나 있고, 교실은 N×N 크기의 격자로 나타낼 수 있다. 학교에 다니는 학생의 수는 N2명이다. 오늘은 모든 학생의 자리를 정하는 날이다. 학생은 1번부터 N2번까지 번호가 매겨져 있고, (r, c)는 r행 c열을 의미한다. 교실의 가장 왼쪽 윗 칸은 (1, 1)이고, 가장 오른쪽 아랫 칸은 (N, N)이다.

선생님은 학생의 순서를 정했고, 각 학생이 좋아하는 학생 4명도 모두 조사했다. 이제 다음과 같은 규칙을 이용해 정해진 순서대로 학생의 자리를 정하려고 한다. 한 칸에는 학생 한 명의 자리만 있을 수 있고, |r1 - r2| + |c1 - c2| = 1을 만족하는 두 칸이 (r1, c1)과 (r2, c2)를 인접하다고 한다.

  1. 비어있는 칸 중에서 좋아하는 학생이 인접한 칸에 가장 많은 칸으로 자리를 정한다.
  2. 1을 만족하는 칸이 여러 개이면, 인접한 칸 중에서 비어있는 칸이 가장 많은 칸으로 자리를 정한다.
  3. 2를 만족하는 칸도 여러 개인 경우에는 행의 번호가 가장 작은 칸으로, 그러한 칸도 여러 개이면 열의 번호가 가장 작은 칸으로 자리를 정한다.

예를 들어, N = 3이고, 학생 N2명의 순서와 각 학생이 좋아하는 학생이 다음과 같은 경우를 생각해보자.

 

학생의 번호                                     좋아하는 학생의 번호
4 2, 5, 1, 7
3 1, 9, 4, 5
9 8, 1, 2, 3
8 1, 9, 3, 4
7 2, 3, 4, 8
1 9, 2, 5, 7
6 5, 2, 3, 4
5 1, 9, 2, 8
2 9, 3, 1, 4

가장 먼저, 4번 학생의 자리를 정해야 한다. 현재 교실의 모든 칸은 빈 칸이다. 2번 조건에 의해 인접한 칸 중에서 비어있는 칸이 가장 많은 칸인 (2, 2)이 4번 학생의 자리가 된다.

     
   4   
     

다음 학생은 3번이다. 1번 조건을 만족하는 칸은 (1, 2), (2, 1), (2, 3), (3, 2) 이다. 이 칸은 모두 비어있는 인접한 칸이 2개이다. 따라서, 3번 조건에 의해 (1, 2)가 3번 학생의 자리가 된다.

  3  
   4   
     

다음은 9번 학생이다. 9번 학생이 좋아하는 학생의 번호는 8, 1, 2, 3이고, 이 중에 3은 자리에 앉아있다. 좋아하는 학생이 가장 많이 인접한 칸은 (1, 1), (1, 3)이다. 두 칸 모두 비어있는 인접한 칸이 1개이고, 행의 번호도 1이다. 따라서, 3번 조건에 의해 (1, 1)이 9번 학생의 자리가 된다.

9 3  
  4   
     

이번에 자리를 정할 학생은 8번 학생이다. (2, 1)이 8번 학생이 좋아하는 학생과 가장 많이 인접한 칸이기 때문에, 여기가 그 학생의 자리이다.

9 3  
8 4   
     

7번 학생의 자리를 정해보자. 1번 조건을 만족하는 칸은 (1, 3), (2, 3), (3, 1), (3, 2)로 총 4개가 있고, 비어있는 칸과 가장 많이 인접한 칸은 (2, 3), (3, 2)이다. 행의 번호가 작은 (2, 3)이 7번 학생의 자리가 된다.

9 3  
8 4 7
     

이런식으로 학생의 자리를 모두 정하면 다음과 같다.

9 3 2
8 4 7
5 6 1

이제 학생의 만족도를 구해야 한다. 학생의 만족도는 자리 배치가 모두 끝난 후에 구할 수 있다. 학생의 만족도를 구하려면 그 학생과 인접한 칸에 앉은 좋아하는 학생의 수를 구해야 한다. 그 값이 0이면 학생의 만족도는 0, 1이면 1, 2이면 10, 3이면 100, 4이면 1000이다.

학생의 만족도의 총 합을 구해보자.

입력

첫째 줄에 N이 주어진다. 둘째 줄부터 N2개의 줄에 학생의 번호와 그 학생이 좋아하는 학생 4명의 번호가 한 줄에 하나씩 선생님이 자리를 정할 순서대로 주어진다.

학생의 번호는 중복되지 않으며, 어떤 학생이 좋아하는 학생 4명은 모두 다른 학생으로 이루어져 있다. 입력으로 주어지는 학생의 번호, 좋아하는 학생의 번호는 N2보다 작거나 같은 자연수이다. 어떤 학생이 자기 자신을 좋아하는 경우는 없다.

출력

첫째 줄에 학생의 만족도의 총 합을 출력한다.

제한

  • 3 ≤ N ≤ 20

예제 입력 1

3
4 2 5 1 7
3 1 9 4 5
9 8 1 2 3
8 1 9 3 4
7 2 3 4 8
1 9 2 5 7
6 5 2 3 4
5 1 9 2 8
2 9 3 1 4

예제 출력 1

54

예제 입력 2

3
4 2 5 1 7
2 1 9 4 5
5 8 1 4 3
1 2 9 3 4
7 2 3 4 8
9 8 4 5 7
6 5 2 3 4
8 4 9 2 1
3 9 2 1 4

예제 출력 2

1053

 

삼성 역량테스트의 '상어 시리즈' 문제이다.

별다른 알고리즘을 요구하지 않고 단순히 문제에서 요구하는대로 구현하면되는 문제이다. 굳이 문제의 분류를 하자면 구현, 정렬, 등이 있겠다.

 

구현 문제는 문제에서 주어진 순서에 맞춰 차례대로 코드를 구현하고 함수를 실행하면 된다.

문제의 요구사항을 아래와 같이 간략화 시켜보았다.

 

#0. 모든 칸을 순위하며 해당 학생이 앉을 자리 찾기

#1. 인접한 칸 중 해당 학생이 좋아하는 학생의 수와, 빈 칸의 수를 배열에 각각 기록

#1.1. 배열에 기록할 값 => (좋아하는 학생의 수, 빈 칸의 수, 행의 번호, 열의 번호)

#2. 배열을 조건1~3에 맞춰 정렬한 후, 조건을 만족하는 자리에 배치

#3. 모든 학생들을 배치한 , 배열을 돌면서 전체 만족도 계산

 

문제의 요구사항에 따르면 학생들을 배치하는 과정은 아래와 같은 순서를 따른다.

  1. 비어있는 칸 중에서 좋아하는 학생이 인접한 칸에 가장 많은 칸으로 자리를 정한다.
  2. 1을 만족하는 칸이 여러 개이면, 인접한 칸 중에서 비어있는 칸이 가장 많은 칸으로 자리를 정한다.
  3. 2를 만족하는 칸도 여러 개인 경우에는 행의 번호가 가장 작은 칸으로, 그러한 칸도 여러 개이면 열의 번호가 가장 작은 칸으로 자리를 정한다.

따라서 위 3조건을 만족하는 모든 좌표를 배열에 기록해둔다음 위 조건에 맞춰서 배열을 정렬시킨후 가장 앞서는 값의 좌표를 학생이 앉을 좌표로 설정하면 된다.

 

파이썬에서 정렬은 sorted와 sort로 수행되며 위 문제에서 처럼 정렬 기준을 사용자가 원하는 기준대로 설정하기 위해선 람다식을 사용해야한다.

예를 들어 배열에 저장된 형태가 (좋아하는 학생의 수, 빈 영역의 수, 행의 번호, 열의 번호) 라면, 좋아하는 학생 수와 빈 영역의 수는 내림차순으로 정렬하고, 좌표값은 내림차순으로 정렬해야 하므로 아래와 같이 -기호를 사용하여 정렬 기준을 설정한다.

seat_info.sort(key = lambda x: (-x[0],-x[1],x[2],x[3]))

 

위와 같이 작성한다면 x의 0,1,2,3번째 인덱스의 값을 바탕으로 정렬을 수행하며 앞에 위치하는 인덱스가 정렬에서 우선시 되며 뒤에 위치하는 인덱스는 동일한 값이 있을 경우에 대한 정렬기준이 된다.

 

이 아이디어들을 코드로 옮기면 아래와 같다.

# https://www.acmicpc.net/problem/21608
N = int(input())
seat = [[0]*N for _ in range(N)]
dx = [0,0,-1,1]
dy = [-1,1,0,0]

# n번째 학생이 앉을 자리 계산
def select_seat():
    global seat
    # 각 좌석에 대한 정보를 기록하기 위한 배열 생성
    seat_info = []
    for x in range(N):
        for y in range(N):
            if seat[x][y] != 0:
                continue
            like_count,empty_count = 0,0 # 좋아하는 학생수, 빈좌석수
            # 앉을 수 있는 자리에 대해서 상하좌우 검사
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                if 0<=nx<N and 0<=ny<N:
                    if seat[nx][ny] in like: # 좋아하는 학생이라면
                        like_count+=1
                    elif seat[nx][ny] == 0: # 빈 좌석이라면
                        empty_count+=1
            # 현재 좌석에 대한 정보 배열에 기록
            seat_info.append([like_count,empty_count,x,y])
    
    # 최적의 자리 계산하여 배치
    # 1. 좋아하는 사람이 가장 많은 자리로
    # 2. 그 중에서 주변에 빈칸이 많은 자리로
    # 3. 그 중에서 행의 번호가 가장 작고, 그 다음으로 열의 번호가 가장 작은 순으로
    # 내림차순,내림차순,오름차순,오름차순
    seat_info.sort(key = lambda x: (-x[0],-x[1],x[2],x[3])) 
    
    # 정렬 이후 가장 왼쪽에 위치한 값의 x,y좌표
    x = seat_info[0][2]
    y = seat_info[0][3]
    
    # 그 좌표에 사람 배치
    seat[x][y] = n


# 전체 만족도 계산
def calc_satisfication():
    result = 0 # 만족도 총합
    for x in range(N):
        for y in range(N):
            p = seat[x][y] # 그 칸에 있는 사람의 번호
            like = people[p] # 그 사람이 좋아하는 사람들
            like_count = 0 # 좋아하는 사람의 수
            for i in range(4):
                nx = x + dx[i]
                ny = y + dy[i]
                if 0<=nx<N and 0<=ny<N:
                    if seat[nx][ny] in like:
                        like_count += 1
            if like_count == 1:
                result += 1
            elif like_count == 2:
                result += 10
            elif like_count == 3:
                result += 100
            elif like_count == 4:
                result += 1000
    return result

people = [[] for _ in range(N*N+1)] # N번 사람이 각각 누구를 좋아하는지 기록 => 호감도 계산에 사용
for _ in range(N*N):
    data = list(map(int,input().split()))
    n,like = data[0],data[1:]
    people[n] = like
    select_seat()

print(calc_satisfication())

 

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

 

21608번: 상어 초등학교

상어 초등학교에는 교실이 하나 있고, 교실은 N×N 크기의 격자로 나타낼 수 있다. 학교에 다니는 학생의 수는 N2명이다. 오늘은 모든 학생의 자리를 정하는 날이다. 학생은 1번부터 N2번까지 번호

www.acmicpc.net

 

728x90

한윤정이 이탈리아에 가서 아이스크림을 사먹는데 

1 초 128 MB 11014 4434 3345 40.306%

문제

한윤정과 친구들은 이탈리아로 방학 여행을 갔다. 이탈리아는 덥다. 윤정이와 친구들은 아이스크림을 사먹기로 했다. 아이스크림 가게에는 N종류의 아이스크림이 있다. 모든 아이스크림은 1부터 N까지 번호가 매겨져있다. 어떤 종류의 아이스크림을 함께먹으면, 맛이 아주 형편없어진다. 따라서 윤정이는 이러한 경우를 피하면서 아이스크림을 3가지 선택하려고 한다. 이때, 선택하는 방법이 몇 가지인지 구하려고 한다.

입력

첫째 줄에 정수 N과 M이 주어진다. N은 아이스크림 종류의 수이고, M은 섞어먹으면 안 되는 조합의 개수이다. 아래 M개의 줄에는 섞어먹으면 안 되는 조합의 번호가 주어진다. 같은 조합은 두 번 이상 나오지 않는다. (1 ≤ N ≤ 200, 0 ≤ M ≤ 10,000)

출력

첫째 줄에, 가능한 방법이 총 몇 개 있는지 출력한다.

예제 입력 1

5 3
1 2
3 4
1 3

예제 출력 1

3

힌트

5개의 아이스크림과 3가지 섞어먹으면 안 되는 조합이 있다. 1번은 2번 3번과 섞으면 안되고, 3번은 4번과 섞으면 안 된다. 따라서 (1 4 5), (2 3 5), (2 4 5)와 같이 3가지 방법이 있다.

 

 

재귀를 사용한 백트래킹 기법으로 해결이 가능한 문제다.

먼저 입력에 맞춰서 섞으면 안되는 아이스크림을 bool타입 2차원 배열의 형태로 저장한다.

예를들어 1번과 3번 아이스크림을 섞어 먹을 수 없다면, cantMix[1][3] = true;, cantMix[3][1] = true; 와 같이 2차원배열에 저장해두고 1번 아이스크림부터 N번 아이스크림까지 반복문을 돌면서 고를 수 있는 아이스크림 3개를 골라 경우의 수를 카운팅하면 된다.

 

아이스크림을 고를 수 있는지 없는지 확인하기 위해 현재까지 고른 아이스크림을 저장하기 위한 배열을 별도로 만들고, 배열에 포함된 아이스크림들(현재까지 선택한 아이스크림들)과 고르고 싶은 아이스크림을 섞을 수 있는지 비교한 후 저장한다. 이 과정을 끝까지 반복하면 선택가능한 모든 경우의 수를 계산할 수 있다.

 

위 아이디어를 코드로 옮기면 아래와 같다.

 

#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int n,m,result = 0;
bool cantMix[201][201]; // 수의 범위가 작으므로 맵보다 배열이 유리
vector<int> selected;

/**
 * 같이 먹으면 안되는 아이스크림 조합 생성
 * 앞에서부터 아이스크림을 하나 고르고, 그 아이스크림과 함께 골라도 되는 경우만 세어서 3개 고를때까지 반복 => 숫자 세기
*/
// 몇개 골랐는지, 어떤 인덱스까지 탐색했는지(다음 인덱스부터 탐색)
void selectIce(int start){
    if(selected.size()==3){ // 3개를 골랐다면
        result++;
        return;
    }
    for(int i=start+1;i<n+1;i++){ // 1번부터 n번까지의 아이스크림에 대해
        // i번째 아이스크림을 현재 집합에 넣을 수 있는지 확인
        bool able = true; // 넣을 수 있음
        // 현재 집합에 포함된 수들과 합쳐서 넣어도 되는지 확인
        for(int j=0;j<selected.size();j++){
            if(cantMix[i][selected[j]]){ // i와 selected의 j번째 인덱스의 인자를 섞을 수 없다면
                able = false;
                break;
            }
        }
        if(able){ // 넣을수 있다면
            selected.push_back(i);
            selectIce(i);
            selected.pop_back();
        } 
    }
}


int main(){
    cin.tie(NULL) -> ios::sync_with_stdio(false);
    cin>>n>>m;
    int a,b;
    for(int i=0;i<m;i++){
        cin>>a>>b;
        // 섞어먹으면 안되는 조합들
        cantMix[a][b] = true;
        cantMix[b][a] = true;
    }
    selectIce(0); // 아이스크림 고르기
    cout<<result<<endl;
}

 

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

 

2422번: 한윤정이 이탈리아에 가서 아이스크림을 사먹는데

첫째 줄에 정수 N과 M이 주어진다. N은 아이스크림 종류의 수이고, M은 섞어먹으면 안 되는 조합의 개수이다. 아래 M개의 줄에는 섞어먹으면 안 되는 조합의 번호가 주어진다. 같은 조합은 두 번

www.acmicpc.net

728x90

개똥벌레 

1 초 128 MB 17347 7616 5606 45.028%

문제

개똥벌레 한 마리가 장애물(석순과 종유석)로 가득찬 동굴에 들어갔다. 동굴의 길이는 N미터이고, 높이는 H미터이다. (N은 짝수) 첫 번째 장애물은 항상 석순이고, 그 다음에는 종유석과 석순이 번갈아가면서 등장한다.

아래 그림은 길이가 14미터이고 높이가 5미터인 동굴이다. (예제 그림)

 

이 개똥벌레는 장애물을 피하지 않는다. 자신이 지나갈 구간을 정한 다음 일직선으로 지나가면서 만나는 모든 장애물을 파괴한다.

위의 그림에서 4번째 구간으로 개똥벌레가 날아간다면 파괴해야하는 장애물의 수는 총 여덟개이다. (4번째 구간은 길이가 3인 석순과 길이가 4인 석순의 중간지점을 말한다)

 

하지만, 첫 번째 구간이나 다섯 번째 구간으로 날아간다면 개똥벌레는 장애물 일곱개만 파괴하면 된다.

동굴의 크기와 높이, 모든 장애물의 크기가 주어진다. 이때, 개똥벌레가 파괴해야하는 장애물의 최솟값과 그러한 구간이 총 몇 개 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N과 H가 주어진다. N은 항상 짝수이다. (2 ≤ N ≤ 200,000, 2 ≤ H ≤ 500,000)

다음 N개 줄에는 장애물의 크기가 순서대로 주어진다. 장애물의 크기는 H보다 작은 양수이다.

출력

첫째 줄에 개똥벌레가 파괴해야 하는 장애물의 최솟값과 그러한 구간의 수를 공백으로 구분하여 출력한다.

예제 입력 1

6 7
1
5
3
3
5
1

예제 출력 1

2 3

예제 입력 2

14 5
1
3
4
2
2
4
3
4
3
3
3
2
3
3

예제 출력 2

7 2

 

이분탐색을 이용하여 풀 수 있는 문제이다.

정답으로 최소 횟수와, 최소 횟수를 만족하는 구간의 수를 출력해야하므로, 이분 탐색을 통해 최소 횟수를 카운팅하여 갱신하고 각 구간별로 카운트 횟수를 배열을 만들어 기록한다.

이후 count()를 이용하여 해당 배열에서 최소값과 같은 항목의 개수를 출력하면 문제의 출력 조건을 만족하게 된다.

 

각 구간별로 부딪히는 횟수를 카운팅 하는 아이디어는 아래와 같다.

 

석순과 종유석을 분리하여 별도의 2개의 배열에 저장한다.

석순은 그대로 저장하고, 종유석은 높이-값의 형태로 저장한다. (해당 구간에 충돌했는지 여부를 알기 위해서이다.)

위 조건대로 배열에 값을 저장하고, 배열을 정렬시킨후 1번 구간부터 H구간까지의 이분 탐색을 수행한다.

이때 이분탐색은 bisect 라이브러리를 이용하며, 리턴값은 아래와 같다

 

 

해당 값이 리스트에 있을 때

bisect_left - 해당 index 반환

bisect_right - 해당 index+1 반환

 

해당 값이 리스트에 없을 때

bisect_left - 리스트 오름차순에 들어갈 index 반환

bisect_right - 리스트 오름차순에 들어갈 index 반환

 

1. 석순의 부딪히는 횟수 처리 (짝수번째 인덱스)

1,3,5,7,3,3,4,2 가정시 => [1,2,3,3,3,4,5,7] 와 같은 형태로 저장한후 배열을 정렬한다.

개똥벌레가 탐색하는 위치가 3이라고 가정시 , 3이랑 같거나 큰 값은 모두 충돌하고, 그 이하의 값에서는 충돌하지 않는다.

즉 위의 문제의 경우 여러번 등장하는 3들중 가장 왼쪽 인덱스인 2번째 인덱스 값부터 가장 오른쪽 인덱스 값까지 충돌하게 된다.

=> bisect_left(array,n) 을 이용하여 배열에서 n과 같은 값들중 가장 왼쪽의 인덱스를 리턴받은후, 배열의 크기에서 해당 값을 빼면 몇번 충돌했는지 알 수 있다.

 

 

2.종유석이 부딪히는 횟수 처리 (홀수번째 인덱스)

3,3,4,5,1,2 가정시( H = 5) => [2,2,1,0,4,3]가 되고, 정렬하면[0,1,2,2,3,4]이 된다.

위 배열의 값을 해석하면, 값이 2라고 할때 2보다 작은 값들은 2번 구간을 지나갈때 충돌하지 않는다는 것이다.

개똥벌레가 탐색하는 위치가 3라고 가정시, 배열에서 3에 해당하는 값들은 지나 갈 수 있으므로 3-1 = 2보다 작거나 같은 값들에서 충돌한다.

=> bisect_right(array,n)을 이용하게 되면 2와 일치하는 값들 중 가장 오른쪽 index에서 index+1의 값을 리턴받게되므로 index의 수가 충돌한 종유석의 수와 같게된다.

 

1, 2번 과정을 각 구간마다 수행하여 최종 부딪히는 횟수를 기록하고, 최소값을 갱신하여 마지막에 비교후 출력하게 되면 정답 판정을 받을 수 있다.

 

위 아이디어를 적용한 코드는 아래와 같다.

import sys
from bisect import bisect_left,bisect_right # 이분탐색을 도와주는 라이브러리
N,H = map(int,input().split()) # N개의 장애물이 존재(짝수인덱스는 석순, 홀수 인덱스는 종유석), H는 높이

stalagmite = [] # 석순
stalactite = [] # 종유석

for i in range(N):
    size = int(input())
    if i%2==0: # 석순이면 석순 배열에
        stalagmite.append(size)
    else: # 종유석이면 종유석 배열에 H-value로 삽입
        stalactite.append(H-size)
        
# 입력이 끝나면 각 배열 정렬
stalagmite.sort() # 오름차순 정렬
stalactite.sort() # 오름차순 정렬

# 각 배열의 크기
stalagmite_size = len(stalagmite)
stalactite_size = len(stalactite)

min_count = sys.maxsize # 최소 횟수
# 층별로 부딪히는 횟수를 저장하기 위한 배열
crash_count = []

# 높이 1부터 높이 H까지 각 배열에 대해서 이분탐색 수행
for i in range(1,H+1):
    count = 0 # 장애물과 부딪히는 횟수 초기화
    
    # 석순과 부딪히는 횟수(i와 크거나 같을때)
    index = bisect_left(stalagmite,i) # 해당 값과 동일한 값들 중, 가장 왼쪽 인덱스
    count += stalagmite_size-index # 전체 석순 개수에서 빼주면 몇개 충돌하는지 알 수 있음
    
    # 종유석 충돌 계산
    index = bisect_right(stalactite,i-1) # 오른쪽 값 반환
    count += index # index가 충돌 횟수가 됨
    
    crash_count.append(count) # 부딪힌 횟수 기록
    min_count = min(min_count,count) # 최소 횟수 업데이트
    
print(min_count,end=' ')
# 최소횟수인 값의 수 출력
print(crash_count.count(min_count))
 
 

 

728x90

행복 유치원 

1 초 512 MB 7961 4373 3593 55.593%

문제

행복 유치원 원장인 태양이는 어느 날 N명의 원생들을 키 순서대로 일렬로 줄 세우고, 총 K개의 조로 나누려고 한다. 각 조에는 원생이 적어도 한 명 있어야 하며, 같은 조에 속한 원생들은 서로 인접해 있어야 한다. 조별로 인원수가 같을 필요는 없다.

이렇게 나뉘어진 조들은 각자 단체 티셔츠를 맞추려고 한다. 조마다 티셔츠를 맞추는 비용은 조에서 가장 키가 큰 원생과 가장 키가 작은 원생의 키 차이만큼 든다. 최대한 비용을 아끼고 싶어 하는 태양이는 K개의 조에 대해 티셔츠 만드는 비용의 합을 최소로 하고 싶어한다. 태양이를 도와 최소의 비용을 구하자.

입력

입력의 첫 줄에는 유치원에 있는 원생의 수를 나타내는 자연수 N(1 ≤ N ≤ 300,000)과 나누려고 하는 조의 개수를 나타내는 자연수 K(1 ≤ K ≤ N)가 공백으로 구분되어 주어진다. 다음 줄에는 원생들의 키를 나타내는 N개의 자연수가 공백으로 구분되어 줄 서 있는 순서대로 주어진다. 태양이는 원생들을 키 순서대로 줄 세웠으므로, 왼쪽에 있는 원생이 오른쪽에 있는 원생보다 크지 않다. 원생의 키는 109를 넘지 않는 자연수이다.

출력

티셔츠 만드는 비용이 최소가 되도록 K개의 조로 나누었을 때, 티셔츠 만드는 비용을 출력한다.

예제 입력 1

5 3
1 3 5 6 10

예제 출력 1

3

힌트

  • 1조: 1, 3
  • 2조: 5, 6
  • 3조: 10

 

문제를 풀기 위한 핵심 코드는 '인접한 사람들' 끼리 조를 이룬다는 점이다.

아래와 같이 사람이 6명 있다고 가정해보자. 편의를 위해 사람을 0으로 표현한다.

 

0,0,0,0,0,0

 

이 6명의 사람을 3개의 조로 나눈다고 생각해보면 다음과 같이 생각할 수 있다.

 

0/0,0/0,0,0

0,0/0,0/0,0

0/0,0,0,0/0

...

 

'인접한 사람들' 끼리 조를 나누는 것 이므로 입력받은 사람들 사이에 경계를 고른다고 생각하면 3개의 조로 나눌때는 2개의 경계를 고르면 된다는 것을 알 수 있다. 즉 k개의 조로 나누려면 사람들 사이를 가를 k-1개의 경계를 고르면 된다.

 

다음으로 가장 키가 큰 사람과 가장 키가 작은 사람의 키 차이를 어떻게 구할 지 생각해보자.

1,3,5,6와 같이 사람들이 서 있다고 할때,(각 숫자는 사람의 키를 의미한다.) 인접한 사람들 사이의 키 차이를 기록해 보면 2,2,1과 같이 표현 할 수 있다. 이 그룹에서 1,3의 키 차이는 2이고 3,5의 키 차이는 2이고, 5,6의 키 차이는 1이므로, 결국 키가 가장 작은사람(1)과 키가 가장 큰 사람(6)의 키 차이는 2+2+1 = 5 임을 알 수 있다.

이것을 앞서 말한 경계 설정과 함께 해석해보면, 인접한 사람들 사이의 값이 크면 그 값을 경계로 삼으면 된다는 힌트를 얻을 수 있다.

 

 

따라서 문제를 풀기 위해선 아래와 같은 순서를 따르면 된다.

1. 인접한 사람들 사이의 키 차이 배열을 만든다. (각 키 차이는 두 사람 사이의 '경계'가 된다.)

2. 키 차이 배열을 오름차순정렬 한 뒤, 가장 큰 k-1개의 경계를 고른다.

(각 조에서의 키 차이의 합이 최소가 되도록 하는것이 목표이므로, 가장 큰 k-1개를 경계로 삼으면 두 사람 사이의 키 차이는 무시할수 있다.)

=> 예를 들어 1,4,2,7,8,10 와 같이 있을때 인접한 키 차이 배열은 3,2,5,1,2 이 되고, 이 중에서 경계로 5와 3을 지정하면 1/4,2/7,8,10 으로 조가 나뉘게 된다.

3. 이후 k-1개의 값을 무시하고 나머지 값들을 더하면 이 값이 각 조의 최소비용의 합이 된다.

=> 각각의 키 차이는 0/2/3이 되어 합은 5가 된다.(1+2+2 = 5)

 

위 아이디어를 정리한 코드와 주석은 아래와 같다.

# https://www.acmicpc.net/problem/13164
'''
인접한 사람들끼리의 키의 합을 최소로 하기 위해선
조를 나눌 '경계'를 잘 설정해야함
6명을 3개의 조로 나누는 상황을 가정할때
0/0,0,0,0/0
0,0/0,0/0,0
0,0/0,0,0/0
등으로 나눌 수 있고, 이는 경계를 2개 골라야 함을 의미한다
=> n명을 k개의 조로 나누면 k-1만큼의 경계를 골라야 한다.

사람이 여러명 있을때 가장 큰사람과 가장 작은 사람의 키 차이는
인접한 사람의 키 차이를 더한 값과 같다
1,3,5 와 같이 있을때, 1,3의 차이는 2이고 3,5의 차이는 2이므로
1,3,5 그룹에서 가장 큰 사람과 가장 작은 사람의 키 차이는 4이다.

이를 이용해서 모든 인접한 사람들의 키 차이를 구해 저장한 배열을 만들고
오름차순으로 정렬한다.
6명의 경우 인접한 키 차이 배열은 5개의 인덱스를 갖게 될 것이고
3개의 그룹으로 나눈다고 할 때, 5개의 인덱스 중 2개의 인덱스는 경계 값으로 무시 될 수 있다.

1,4,2,7,8,10 와 같이 있을때 인접한 키 차이 배열은
3,2,5,1,2 이 되고, 이 중에서 경계로 5와 3을 지정하면
1/4,2/7,8,10 으로 조가 나뉘게 되고, 각각의 키 차이는 0/2/3이 되어 합은 5가 된다.
따라서 키 차이 배열 3,2,5,1,2를 오름차순 정렬한 뒤(1,2,2,3,5)
이 중에서 가장 큰 k-1개의 값들을 무시하고 더하면 최소값이 된다.(1+2+2 = 5) 
'''
n,k = map(int,input().split())
# 키 배열
h = list(map(int,input().split()))
# 인접한 키 배열
array = []
for i in range(1,len(h)):
    array.append(h[i]-h[i-1])
    
# 인접한 키 배열 오름차순 정렬
array.sort()

# 가장 큰 k-1개 무시하고 나머지 값들 합하여 출력
# n-1 - (k-1) = n-k
print(sum(array[:n-k]))
 

 

 

 

728x90

색종이 만들기 

1 초 128 MB 43829 30485 23505 69.554%

문제

아래 <그림 1>과 같이 여러개의 정사각형칸들로 이루어진 정사각형 모양의 종이가 주어져 있고, 각 정사각형들은 하얀색으로 칠해져 있거나 파란색으로 칠해져 있다. 주어진 종이를 일정한 규칙에 따라 잘라서 다양한 크기를 가진 정사각형 모양의 하얀색 또는 파란색 색종이를 만들려고 한다.

전체 종이의 크기가 N×N(N=2k, k는 1 이상 7 이하의 자연수) 이라면 종이를 자르는 규칙은 다음과 같다.

전체 종이가 모두 같은 색으로 칠해져 있지 않으면 가로와 세로로 중간 부분을 잘라서 <그림 2>의 I, II, III, IV와 같이 똑같은 크기의 네 개의 N/2 × N/2색종이로 나눈다. 나누어진 종이 I, II, III, IV 각각에 대해서도 앞에서와 마찬가지로 모두 같은 색으로 칠해져 있지 않으면 같은 방법으로 똑같은 크기의 네 개의 색종이로 나눈다. 이와 같은 과정을 잘라진 종이가 모두 하얀색 또는 모두 파란색으로 칠해져 있거나, 하나의 정사각형 칸이 되어 더 이상 자를 수 없을 때까지 반복한다.

위와 같은 규칙에 따라 잘랐을 때 <그림 3>은 <그림 1>의 종이를 처음 나눈 후의 상태를, <그림 4>는 두 번째 나눈 후의 상태를, <그림 5>는 최종적으로 만들어진 다양한 크기의 9장의 하얀색 색종이와 7장의 파란색 색종이를 보여주고 있다.

입력으로 주어진 종이의 한 변의 길이 N과 각 정사각형칸의 색(하얀색 또는 파란색)이 주어질 때 잘라진 하얀색 색종이와 파란색 색종이의 개수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에는 전체 종이의 한 변의 길이 N이 주어져 있다. N은 2, 4, 8, 16, 32, 64, 128 중 하나이다. 색종이의 각 가로줄의 정사각형칸들의 색이 윗줄부터 차례로 둘째 줄부터 마지막 줄까지 주어진다. 하얀색으로 칠해진 칸은 0, 파란색으로 칠해진 칸은 1로 주어지며, 각 숫자 사이에는 빈칸이 하나씩 있다.

출력

첫째 줄에는 잘라진 햐얀색 색종이의 개수를 출력하고, 둘째 줄에는 파란색 색종이의 개수를 출력한다.

 

예제 입력 1

8
1 1 0 0 0 0 1 1
1 1 0 0 0 0 1 1
0 0 0 0 1 1 0 0
0 0 0 0 1 1 0 0
1 0 0 0 1 1 1 1
0 1 0 0 1 1 1 1
0 0 1 1 1 1 1 1
0 0 1 1 1 1 1 1

예제 출력 1 복사

9
7

 

0은 하얀색을 의미하고, 1은 파란색을 의미한다.

각 색종이의 색을 비교하며 1x1크기가 아닌데, 색이 다른 색종이가 섞여있다면 4등분 하는 과정을 반복하게 된다.

예를 들어 다음과 같은 4x4 크기의 색종이가 있다고 생각해보자.

 

대표사진 삭제

사진 설명을 입력하세요.

4x4크기의 색종이에서 서로 다른 색이 섞여 있으므로, 4등분을 하면 아래와 같은 상태가 된다.

대표사진 삭제

사진 설명을 입력하세요.

이 경우 파란색 색종이 1장, 하얀색 색종이 3장이 생기게 되는 것으로, 문제에서는 이러한 과정을 다양한 입력에 대해서 처리하는 것이다.

 

이 문제는 분할 정복 기법을 이용하여 간단하게 해결할 수 있다.

먼저 사각형의 크기가 1이 되기전까지 사각형을 자르게 되므로, 사각형의 크기가 1인지 확인하고, 1이라면 해당 색의 사각형의 개수를 1 증가시키고, 1이 아니라면 해당 범위내의 사각형이 모두 같은 색인지 확인하도록 한다.

사각형을 자르게 되면 크기가 1/2씩 줄어들게 되므로, 모든 색종이가 같은지 확인하는 탐색 범위도 줄어든 크기만큼 줄일 수 있을것이고,

탐색을 시작하는 범위는 시작점에서 사각형의 크기만큼을 각 좌표에 더하는것으로 지정할수 있다.

 

예를 들어 아래 사각형의 경우, 왼쪽 파란사각형의 경우 가장 왼쪽 위 점인 [0][0]에서 탐색을 시작하여 사각형의 크기인 size만큼 탐색을 진행하고, 각각의 사각형은 x좌표, y좌표, 혹은 x,y좌표 둘다에 size만큼을 더하게 되면 파란 사각형처럼 가장 왼쪽 위 좌표를 가리키게 될것이고, 그곳에서부터 다시 size만큼 이동하며 탐색을 진행하도록 한다.

대표사진 삭제

사진 설명을 입력하세요.

각 영역을 모두 분석하여 색이 모두 같다면 해당 색의 카운트를 1증가시키고, 만약 색이 하나라도 다르다면 문제의 조건대로 4등분하여 앞서 말한 과정을 처음부터 반복하도록 한다.

 

위의 과정은 재귀함수 호출을 통해 탐색 시작좌표와, 사각형의 크기를 매개변수로 넘겨줌으로써 쉽게 구현할수 있으며 말로 설명하는 것보다 코드를 보는것으로 쉽게 이해 할 수 있을것이라고 생각된다.

# https://www.acmicpc.net/problem/2630
n = int(input())

data = [list(map(int, input().split())) for _ in range(n)]

count = [0, 0]  # 흰색종이, 파란색종이


# 분할정복
def cut(s_x, s_y, size):
    color = data[s_x][s_y]
    # 색종이의 크기가 1x1이라면 종료
    if size == 1:
        # 해당 색종이 색의 개수를 늘리고 종료
        count[color] += 1
        return
    # 모든 색종이의 색이 같다면 자를필요 없이 종료
    error = False
    for i in range(s_x, s_x+size):
        for j in range(s_y, s_y+size):
            if data[i][j] != color:
                error = True
                break
        if error:
            break
    if not error:
        count[color] += 1  # 에러가 없다 => 모든색이 일치한다 => 개수증가
        return
    # 리턴을 하지 못했다면 에러가 있었다는 의미, 즉 색이 다른 색종이가 있었다
    size //= 2
    cut(s_x, s_y, size)
    cut(s_x, s_y+size, size)
    cut(s_x+size, s_y,  size)
    cut(s_x+size, s_y+size, size)


cut(0, 0, n)
# 정답 출력
print(count[0])
print(count[1])
 

 

728x90

Yonsei TOTO 

1 초 128 MB 5788 2072 1723 34.738%

문제

연세대학교 수강신청이 얼마 전부터 바뀌어, 마일리지 제도로 바뀌었다. 이 제도는 각각의 학생들에게 마일리지를 주어 듣고 싶은 과목에 마일리지를 과목당 1~36을 분배한다. 그리고 모두 분배가 끝이 나면 과목에 대해서 마일리지를 많이 투자한 순으로 그 과목의 수강인원만큼 신청되는 방식이다.

성준이는 연세대학교 재학 중인 학생이다. 성준이는 저번 수강신청에서 실패하여 휴학을 했기 때문에 이번 수강신청만은 필사적으로 성공하려고 한다. 그래서 성준이는 학교 홈페이지를 뚫어버렸다.

그 덕분에 다른 사람들이 신청한 마일리지를 볼 수 있게 되었다. 성준이는 주어진 마일리지로 최대한 많은 과목을 신청하고 싶어 한다. (내가 마일리지를 넣고 이후에 과목을 신청하는 사람은 없다) 마일리지는 한 과목에 1에서 36까지 넣을 수 있다.

입력

첫째 줄에는 과목 수 n (1 ≤ n ≤ 100)과 주어진 마일리지 m (1 ≤ m ≤ 100)이 주어진다. 각 과목마다 2줄의 입력이 주어지는데 첫째 줄에는 각 과목에 신청한 사람 수 Pi과 과목의 수강인원 Li이 주어지고 그 다음 줄에는 각 사람이 마일리지를 얼마나 넣었는지 주어진다. (1 ≤ Pi ≤100, 1 ≤ Li ≤ 100)

(단 마일리지가 같다면 성준이에게 우선순위가 주어진다고 하자.)

출력

첫째 줄에 주어진 마일리지로 최대로 들을 수 있는 과목 개수를 출력한다.

예제 입력 1 

5 76
5 4 
36 25 1 36 36
4 4
30 24 25 20
6 4
36 36 36 36 36 36
2 4
3 7
5 4
27 15 26 8 14

예제 출력 1

4

 

 

정렬을 활용하여 풀 수 있는 문제이다.

 

최대한 많은 과목을 수강신청 하기 위해서는, 수강신청에 필요한 '가장 적절한 가격' 에 해당 과목을 입찰하는 것이 중요할 것이다.

예를 들어 5명이 신청한 과목에서 4명을 뽑는다고 가정하고, 36 25 1 36 36 과 같이 사람들이 신청했다고 가정하자.

포인트가 높은 순으로 정렬(내림차순)하면 36,36,36,25,1 과 같이 되는데, 마일리지를 높게 신청한 학생 순서대로 우선순위를 부여해서 수강신청을 해주기 때문에 위 데이터에서 4명을 뽑는다면 36,36,36,25 의 마일러지를 신청한 사람들이 과목을 가져갈 것이다.

 

문제의 조건을 살펴보면 같은 마일리지로 신청하였다면 성준이에게 우선순위가 주어지기 때문에 위 과목을 가져가기 위한 가장 적절한 가격은 25마일리지가 될 것이다.

 

두번째 경우, 신청한 사람들보다 뽑는 사람이 많다고 가정해보자.

예를 들어 4명까지 수강신청이 가능한데 3명의 사람이 신청했다고 한다면, 굳이 높은 마일리지로 입찰할 필요 없이 최소 마일리지인 1마일리지만 사용해도 해당 과목을 가져갈 수 있을것이다.

 

위와 같은 논리로 모든 과목에 대해서 사람들의 마일리지 입찰 데이터를 내림차순으로 정렬한 뒤, 위에서부터 L번째 순번의 가격을 과목을 수강하는데 필요한 최소 마일리지로 책정하여 기록한다.

 

가능한 한 많은 과목을 보유중인 마일리지 내에서 수강하는 것이 최종 목표이므로, 과목을 가져가는데 필요한 마일리지 데이터를 오름차순 정렬한 뒤, 앞에서부터 포인트가 허락하는 한 과목을 수강신청하면 문제를 해결 할 수 있다.

 

위 이론을 적용한 코드는 아래와 같다.

 

# https://www.acmicpc.net/problem/12018

# 목표 : 최대한 많은 과목을 듣는것
# 수강권을 얻는 방법 => 많이 투자한 순에서 해당 과목의 수강신청 인원만큼 받아줌
# => 가장 적절한 값으로 수강에 성공하는 것이 중요
# 
# 아이디어 : 다른 사람들이 입력한 가격을 내림차순으로 정렬
# 정렬한 가격들 중에서, 수강신청 인원의 마지막에 들어가는 것이 가장 적절한 가격
# 가장 이득을 보는 가격을 과목 가격 배열에 삽입
# 과목 가격 배열을 오름차순으로 정렬하고 앞에서부터 차례로 포인트가 만족하는한 과목을 담으면 된다.

import sys
input = sys.stdin.readline

n,m = map(int,input().split()) # 과목 수, 보유중인 마일리지 양

subject_price = [1]*n

for i in range(n):
    p,l = map(int,input().split()) # 신청한 사람 수,과목의 수강 인원
    data = list(map(int,input().split()))
    # 수강 가능 인원이 신청자 보다 많을 경우 => 1원만 입력해도 입찰이 가능
    if p<l:
        continue # 넘어감 => 1원
    data.sort(reverse=True) # 내림차순 정렬 => 위에서부터 컷
    # 적정 가격은 l-1번째 사람이 입찰한가격과 같은 값(같은 값이면 우선순위가 높으므로)
    subject_price[i] = data[l-1]

# 몇과목을 수강할 수 있는지 계산
count = 0 # 들을 수 있는 가격의 수
subject_price.sort()
for price in subject_price:
    if m-price >= 0: # 현재 마일리지에서 수강 가능하면
        count+=1 # 수강가능
        m-=price # 수강하기
    else:
        break # 더이상 못듣는다면 탐색 종료
print(count)
 

 

728x90

도영이가 만든 맛있는 음식 

1 초 128 MB 15352 8516 6219 54.022%

문제

도영이는 짜파구리 요리사로 명성을 날렸었다. 이번에는 이전에 없었던 새로운 요리에 도전을 해보려고 한다.

지금 도영이의 앞에는 재료가 N개 있다. 도영이는 각 재료의 신맛 S와 쓴맛 B를 알고 있다. 여러 재료를 이용해서 요리할 때, 그 음식의 신맛은 사용한 재료의 신맛의 곱이고, 쓴맛은 합이다.

시거나 쓴 음식을 좋아하는 사람은 많지 않다. 도영이는 재료를 적절히 섞어서 요리의 신맛과 쓴맛의 차이를 작게 만들려고 한다. 또, 물을 요리라고 할 수는 없기 때문에, 재료는 적어도 하나 사용해야 한다.

재료의 신맛과 쓴맛이 주어졌을 때, 신맛과 쓴맛의 차이가 가장 작은 요리를 만드는 프로그램을 작성하시오.

입력

첫째 줄에 재료의 개수 N(1 ≤ N ≤ 10)이 주어진다. 다음 N개 줄에는 그 재료의 신맛과 쓴맛이 공백으로 구분되어 주어진다. 모든 재료를 사용해서 요리를 만들었을 때, 그 요리의 신맛과 쓴맛은 모두 1,000,000,000보다 작은 양의 정수이다.

출력

첫째 줄에 신맛과 쓴맛의 차이가 가장 작은 요리의 차이를 출력한다. 

예제 입력 1

1
3 10

 

예제 출력 1

7

예제 입력 2

2
3 8
5 8

예제 출력 2

1

예제 입력 3

4
1 7
2 6
3 8
4 9

예제 출력 3

1

2, 3, 4번 재료를 사용한다면, 요리의 신맛은 2×3×4=24, 쓴맛은 6+8+9=23이 된다. 차이는 1이다.

 

 

전형적인 브루트포스 문제이다. 재료를 1개부터 n개 선택하는 모든 경우의 수에 대하여, 파이썬 라이브러리 itertools의 combination을 이용하여

신맛과 쓴맛을 계산한후, 두 값의 차이를 기존의 최소값과 비교하는 과정을 반복해주면 된다.

 

예를 들어 주어진 재료(n)가 3개있다고 가정한다면, 재료를 1개부터 3개까지 고를 수 있을것이고 1개를 고를때 나올 수 있는 조합과, 2개를 고를때 나올 수 있는 조합, 3개를 고를 때 나올 수 있는 조합에서 신맛은 서로 곱하고, 쓴맛은 서로 합하여 최종 신맛과 쓴맛을 구한후에 두 값의 차를 절대값으로 하여 기존의 최소값과 비교하는 과정을 반복하는것이다.

 

위 아이디어를 코드로 옮기면 다음과 같다.

import sys
from itertools import combinations
n = int(input())
# 재료 정보 (신맛, 쓴맛) 입력받기
food = [list(map(int, input().split())) for _ in range(n)]

result = sys.maxsize  # 신맛과 쓴맛의 최소값

# 재료를 1개부터 n개 선택하는 모든 경우의 수
for i in range(1, n+1):
    # 재료를 i개 선택하는 모든 경우 => selected_food
    for selected_food in combinations(food, i):
        # 신맛, 쓴맛 초기화
        sour, written = 1, 0
        # 선택된 재료들을 대상으로 신맛 쓴맛 계산
        for s, b in selected_food:
            sour *= s  # 신맛은 재료들의 곱
            written += b  # 쓴맛은 재료들의 합
        # 최종적으로 쓴맛과 신맛의 차가 가장 적게 되어야함
        # 기존 최소값과 비교하여 작은값 업데이트
        result = min(abs(sour-written), result)

# 모든 경우의 수를 돌려봤을 때
# 쓴맛과 신맛의 최소값 출력
print(result)
 

 

728x90

 

불켜기

 
시간 제한
메모리 제한
제출
정답
맞힌 사람
정답 비율
2 초
512 MB
7407
2113
1451
27.429%

문제

농부 존은 최근에 N × N개의 방이 있는 거대한 헛간을 새로 지었다. 각 방은 (1, 1)부터 (N,N)까지 번호가 매겨져있다(2 ≤ N ≤ 100). 어둠을 무서워하는 암소 베시는 최대한 많은 방에 불을 밝히고 싶어한다.

베시는 유일하게 불이 켜져있는 방인 (1, 1)방에서 출발한다. 어떤 방에는 다른 방의 불을 끄고 켤 수 있는 스위치가 달려있다. 예를 들어, (1, 1)방에 있는 스위치로 (1, 2)방의 불을 끄고 켤 수 있다. 베시는 불이 켜져있는 방으로만 들어갈 수 있고, 각 방에서는 상하좌우에 인접한 방으로 움직일 수 있다.

베시가 불을 켤 수 있는 방의 최대 개수를 구하시오.

 

입력

첫 번째 줄에는 N(2 ≤ N ≤ 100)과, M(1 ≤ M ≤ 20,000)이 정수로 주어진다.

다음 M줄에는 네 개의 정수 x, y, a, b가 주어진다. (x, y)방에서 (a, b)방의 불을 켜고 끌 수 있다는 의미이다. 한 방에 여러개의 스위치가 있을 수 있고, 하나의 불을 조절하는 스위치 역시 여러개 있을 수 있다.

 

출력

베시가 불을 켤 수 있는 방의 최대 개수를 출력하시오.

 

예제 입력 1

3 6
1 1 1 2
2 1 2 2
1 1 1 3
2 3 3 1
1 3 1 2
1 3 2 1

예제 출력 1

5

 

힌트

(1, 1)방에 있는 스위치로 (1, 2)방과 (1, 3)방의 불을 켤 수 있다. 그리고 (1, 3)으로 걸어가서 (2, 1)방의 불을 켤 수 있다. (2, 1)방에서는 다시 (2, 2)방의 불을 켤 수 있다. (2, 3)방은 어두워서 갈 수 없으므로, (2, 3)위치에 있는 스위치는 누를 수 없다. 그러므로 불을 밝힐 수 있는 방의 최대 개수는 5이다.

 

일반적인 BFS/DFS 문제처럼 그래프에 대한 정보가 주어지는것이 아닌, 일부 노드에 대한 정보가 주어지는 문제이다.

문제를 해석하면, 우선 빛이 없는 공간으로 이동할수 없고 그렇기에 탐색을 시작하는 (1,1)공간 즉, 그래프에서 (0,0)에 해당하는 공간은 빛이 밝혀진 상태로 주어진다. 따라서 빛이 켜졌는지 안켜졌는지 표현하기 위해 light배열이 필요할것이고 한번 방문한 공간을 bfs에서 다시 방문할 수 있으므로 방문정보를 표현하기 위해 visited배열이 필요할것이다.

 

또한 스위치가 여러개 존재할수 있으므로, dictionary자료형을 이용하여 해당 방에 있는 스위치를 배열의 형태로 모두 표현해주고,

스위치가 존재하는 방(dictionary에 key값으로 존재하는 좌표)에 도달하면, key값에 맞는 item의 개수만큼 반복문을 돌면서 킬 수 있는 모든 불을 켜주면 된다.

 

마지막으로 문제를 풀기 위한 핵심 아이디어는 어디까지 방문할수 있는지에 대한것이 아닌, 밝힐수 있는 방의 총 개수가 궁금한 것이므로

아직 불이 안켜진 방의 불을 켰을때, 불이 켜짐으로 인해 새롭게 방문할 수 있는 길이 생길수도 있으므로, 불이 켜진 방의 상하좌우 중, 방문했던적이 있는 좌표를 다시 큐에 삽입함으로써 탐색을 다시 시키면 새로운 공간으로 이동할 수 있으므로 더 많은 불을 밝힐 수 있게 되는것이다.

 

위 아이디어를 코드로 옮기면 아래와 같다.

from collections import deque
# 첫 번째 줄에는 N(2 ≤ N ≤ 100)과, M(1 ≤ M ≤ 20,000)이 정수로 주어진다.
n, m = map(int, input().split())

# 딕셔너리 형태로 정의
switch = {}

# 상하좌우 방향벡터 정의
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]

# 방문정보 확인
visited = [[False]*n for _ in range(n)]

result = 1  # 최초의 방은 불이 켜져있으므로 1부터 시작


# 특정 방의 스위치를 작동시킨 이후, 스위치를 작동시킴으로서 새로이 방을 방문하게 될 가능성이 있으므로
# 다시 상하좌우에 대해 탐색을 진행해주면됨
def bfs():
    global result
    queue = deque()
    queue.append([0, 0])  # 베시는 유일하게 불이 켜져있는 방인 (1, 1)방에서 출발한다
    visited[0][0] = True  # 처음좌표는 방문처리

    while queue:
        x, y = queue.popleft()

        room_n = str(x) + ","+str(y)
        # 해당 방에 스위치가 존재한다면
        if room_n in switch:
            for a, b in switch[room_n]:
                # 아직 불이 안켜져있다면 => 불을 중복으로 키는 경우 방지
                if not light[a][b]:
                    # 스위치를 작동시킨다.
                    light[a][b] = True
                    result += 1  # 작동시킨 스위치 수 증가시키기
                    # 스위치를 작동시켰으므로, 한번 방문하여 이동할수 없음을 확인한 좌표일지라도
                    # 다시 이동할수 있는 수단이 생길 수 있음
                    # 따라서 이미 방문했던 곳이라면 4방향으로의 이동을 다시 한번 수행해야함
                    for i in range(4):
                        na, nb = a+dx[i], b+dy[i]
                        if 0 <= na < n and 0 <= nb < n and visited[na][nb]:
                            queue.append([na, nb])

        # 네 방향으로 이동
        for i in range(4):
            nx = x + dx[i]
            ny = y + dy[i]
            # 범위를 벗어나지 않는지, 불이 켜져 있어서 방문이 가능한 곳인지 확인
            if 0 <= nx < n and 0 <= ny < n and light[nx][ny]:
                # 불이 켜져 있다면 해당 방에 방문
                if not visited[nx][ny]:
                    visited[nx][ny] = True
                    queue.append([nx, ny])


# 방 정보 생성
light = [[False]*n for _ in range(n)]  # 모두 불이 꺼져있다는 의미로?
# 0,0 방은 항상 켜둬야함 => 0,0에서 방문을 시작하기 때문에
light[0][0] = True


# 딕셔너리에 키 삽입하기
for _ in range(m):
    x, y, a, b = map(int, input().split())
    room_number = str(x-1)+","+str(y-1)
    # 기존에 키가 존재한다면 기존 배열에 정보를 추가하여 작성한다.
    if room_number in switch:
        switch[room_number].append([a-1, b-1])
    # 키가 존재하지 않는다면
    else:
        switch[room_number] = [[a-1, b-1]]  # 새롭게 리스트에 정보를 생성한다.

# 0,0 좌표에서 스위치 정보를 이용해 bfs 수행
bfs()
print(result)
 

 

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

 

11967번: 불켜기

(1, 1)방에 있는 스위치로 (1, 2)방과 (1, 3)방의 불을 켤 수 있다. 그리고 (1, 3)으로 걸어가서 (2, 1)방의 불을 켤 수 있다. (2, 1)방에서는 다시 (2, 2)방의 불을 켤 수 있다. (2, 3)방은 어두워서 갈 수 없으

www.acmicpc.net

 

728x90

맥주 마시면서 걸어가기

1 초 128 MB 31995 13136 9639 39.690%

문제

송도에 사는 상근이와 친구들은 송도에서 열리는 펜타포트 락 페스티벌에 가려고 한다. 올해는 맥주를 마시면서 걸어가기로 했다. 출발은 상근이네 집에서 하고, 맥주 한 박스를 들고 출발한다. 맥주 한 박스에는 맥주가 20개 들어있다. 목이 마르면 안되기 때문에 50미터에 한 병씩 마시려고 한다. 즉, 50미터를 가려면 그 직전에 맥주 한 병을 마셔야 한다.

상근이의 집에서 페스티벌이 열리는 곳은 매우 먼 거리이다. 따라서, 맥주를 더 구매해야 할 수도 있다. 미리 인터넷으로 조사를 해보니 다행히도 맥주를 파는 편의점이 있다. 편의점에 들렸을 때, 빈 병은 버리고 새 맥주 병을 살 수 있다. 하지만, 박스에 들어있는 맥주는 20병을 넘을 수 없다. 편의점을 나선 직후에도 50미터를 가기 전에 맥주 한 병을 마셔야 한다.

편의점, 상근이네 집, 펜타포트 락 페스티벌의 좌표가 주어진다. 상근이와 친구들이 행복하게 페스티벌에 도착할 수 있는지 구하는 프로그램을 작성하시오.

입력

첫째 줄에 테스트 케이스의 개수 t가 주어진다. (t ≤ 50)

각 테스트 케이스의 첫째 줄에는 맥주를 파는 편의점의 개수 n이 주어진다. (0 ≤ n ≤ 100).

다음 n+2개 줄에는 상근이네 집, 편의점, 펜타포트 락 페스티벌 좌표가 주어진다. 각 좌표는 두 정수 x와 y로 이루어져 있다. (두 값 모두 미터, -32768 ≤ x, y ≤ 32767)

송도는 직사각형 모양으로 생긴 도시이다. 두 좌표 사이의 거리는 x 좌표의 차이 + y 좌표의 차이 이다. (맨해튼 거리)

출력

각 테스트 케이스에 대해서 상근이와 친구들이 행복하게 페스티벌에 갈 수 있으면 "happy", 중간에 맥주가 바닥나서 더 이동할 수 없으면 "sad"를 출력한다. 

예제 입력 1 

2
2
0 0
1000 0
1000 1000
2000 1000
2
0 0
1000 0
2000 1000
2000 2000

예제 출력 1

happy
sad

 

'맥주를 마시면서 이동한다' 라는 문제 제목때문에, dx,dy를 이용하여 모든 좌표를 하나씩 방문하며 bfs를 수행하다 50m씩 이동할때마다 맥주의 개수를 카운팅 해주어야하나? 라는 생각이 제일 먼저 들었지만 생각해보니 모든 좌표를 하나씩 방문하는것은 말도 안되는 생각이라는 것을 깨달았고, 그 다음으로 들은 생각은 이동하는 좌표를 50m씩 이동하며 큐에 좌표와 함께 현재 시점에서의 맥주의 개수를 삽입해서 bfs 수행시 맥주의 잔여량을 보고 진행하는 방식을 떠올렸으나 좌표값이 50의 배수로 딱 나누어 떨어지지 않을것이고, 그걸 해결할 방법을 찾는다고 하여도 구현이 너무 복잡할것같았고 너무 복잡하게 생각하는게 아닌가 고민하게 되었다.

 

이 문제의 해결방법은 현재위치,편의점의 좌표들, 목적지를 하나의 정점으로 보는것이다.

즉 좌표값들을 각 정점의 요소로 생각하고 결국 방문 하는 장소들은 현재 위치에서 목적지를 향해 이동하거나, 편의점에 방문하였다가 목적지로 이동하거나 둘 중 하나이므로, 정점으로 규정한 현재위치, 편의점, 목적지에 대해서 bfs를 수행하면 된다는것이다.

또한 맥주가 20병이 있다는 소리는, 한번에 최대 1000m를 이동 가능하다는 의미이고(20병 x 50m) 한 정점에서 다른 정점까지의 거리가(두 좌표의 차의 합) 1000이하라면, 해당 좌표를 방문 가능하다는 의미가 될것이다.

 

따라서 내 현재 위치에서 목적지로 바로 방문이 가능한지 확인하고(좌표의 차가 1000이하인지 확인하고), 만약 그렇다면 "happy"를 출력하고, 그렇지 않다면 알고 있는 편의점의 좌표들을 대상으로 마찬가지로 방문이 가능한지 확인하고 bfs를 수행하면서 이동한 좌표들에 대해 목적지로 방문이 가능한지 확인하면된다.

 

앞서 말한 설명들을 코드로 바꾸면 아래와 같다.

from collections import deque
t = int(input())


def bfs(s, e):
    queue = deque()
    queue.append([s, e])

    # 편의점 개수만큼 방문정보를 확인해야하므로 배열 생성
    visited = [False for _ in range(n)]

    while queue:
        x, y = queue.popleft()
        # 현재 위치에서, 목적지에 도달하는것이 가능한지(이동거리 1000으로 이동할수 있는지) 확인
        if (abs(goal_x-x) + abs(goal_y-y)) <= 1000:
            return "happy"  # happy 리턴

        # 목적지에 아직 도달하지 못하였다면, bfs수행
        # 편의점 좌표들 하나씩 방문
        for i in range(n):
            # i번째 편의점 에 아직 방문하지 않은 경우에만 방문
            if not visited[i]:
                # 해당 편의점에 방문이 가능한지 확인(두 좌표의 차의 합이 1000이하인지 확인)
                if (abs(conveni[i][0]-x) + abs(conveni[i][1]-y)) <= 1000:
                    visited[i] = True  # 해당 편의점 방문처리
                    # 편의점의 좌표를 큐에 삽입하여, 상근이를 이동시킴
                    queue.append([conveni[i][0], conveni[i][1]])

    # 탐색 실패시 sad 리턴
    return "sad"


for _ in range(t):
    n = int(input())
    # 현재 상근이의 위치
    s, e = map(int, input().split())
    # 편의점 좌표들
    conveni = [list(map(int, input().split())) for _ in range(n)]
    # 최종 목적지
    goal_x, goal_y = map(int, input().split())
    print(bfs(s, e))
 

 

728x90

이분 그래프 

2 초 256 MB 93062 25289 15511 24.246%

문제

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

그래프가 입력으로 주어졌을 때, 이 그래프가 이분 그래프인지 아닌지 판별하는 프로그램을 작성하시오.

입력

입력은 여러 개의 테스트 케이스로 구성되어 있는데, 첫째 줄에 테스트 케이스의 개수 K가 주어진다. 각 테스트 케이스의 첫째 줄에는 그래프의 정점의 개수 V와 간선의 개수 E가 빈 칸을 사이에 두고 순서대로 주어진다. 각 정점에는 1부터 V까지 차례로 번호가 붙어 있다. 이어서 둘째 줄부터 E개의 줄에 걸쳐 간선에 대한 정보가 주어지는데, 각 줄에 인접한 두 정점의 번호 u, v (u ≠ v)가 빈 칸을 사이에 두고 주어진다. 

출력

K개의 줄에 걸쳐 입력으로 주어진 그래프가 이분 그래프이면 YES, 아니면 NO를 순서대로 출력한다.

제한

  • 2 ≤ K ≤ 5
  • 1 ≤ V ≤ 20,000
  • 1 ≤ E ≤ 200,000

예제 입력 1

2
3 2
1 3
2 3
4 4
1 2
2 3
3 4
4 2

예제 출력 1

YES
NO

 

이 문제를 해결하기 위해선 먼저 이분그래프가 무엇인지부터 알아야한다.

 

이분 그래프란 인접한 정점을 서로 다른 색으로 칠할때 단 2가지 색만을 사용할 수 있는 그래프다.

이 말이 무슨 뜻인지 문제의 예제를 직접 그림으로 그리면서 설명해보겠다.

 

첫번째 예제의 경우 1-3, 3-2 노드가 붙어 있으므로 왼쪽과 같은 그림이 그려지며, 인접한 정점을 빨강,파랑 2가지 색상만을 사용하여 칠할 수 있다. 따라서 이분 그래프이다.

 

 

두번째 예제의 경우를 한번 살펴보자. 먼저 1번 노드를 빨강색으로 칠하고 인접한 3번 노드를 파랑색으로 칠하였다. 이후 3과 인접한 노드인 4번,2번 노드를 둘 다 빨강색으로 칠하였다. 여기서 문제가 발생한다. 4번노드와 2번 노드는 인접한 노드지만 서로 같은 색상이 칠해졌기 때문에 이분 그래프가 될 수 없다.

 

그래프가 이분 그래프인지 아닌지 확인하는 가장 간단한 방법은 그래프 탐색을 이용하는 것이다.

임의의 정점에서 깊이우선탐색을 시작하여 현재 정점과 인접한 정점이 아직 방문하지 않은 정점인 경우 이전 정점과 다른 색상으로 칠하고, 이미 방문한 정점이라면 현재 자신의 색상과 비교하여 같은 색상인지 확인한다. 

이 과정에서 같은 색상이 이미 칠해져있다면 인접한 정점이 서로 같은 색상으로 칠해진다는 의미이므로 이분그래프가 될 수 없고 깊이우선탐색을 문제없이 끝까지 마쳤다면 해당 그래프는 이분 그래프가 된다.

 

이 문제에서 주의해야 할 점은 그래프의 모든 정점이 항상 모두 연결되어있지 않다는 점이다.

 

단편적인 예시로 아래와 같은 형태도 그래프이며 '단절 그래프'라고 부르는데, 단절 그래프에 포함된 그래프 집합을 모두 확인해서 모든 정점이 2가지 색상으로 칠해져야 이분그래프라고 부를 수 있기 때문에 방문하지 않은 모든 정점에 대하여 탐색을 수행해야 한다.

 

이 아이디어를 코드로 옮기면 아래와 같다.

from collections import deque
'''
이분 그래프 => 인접한 정점을 서로 다른 색으로 칠할 수 있는 그래프

<이분 그래프>          <이분 그래프x>
1(R) -  3(B)        1(R) - 2(B)
        |                /   |
        2(R)        3(R) - 4(R)
        
두번재 예시의 경우 4는 인접한 정점 3과 같은 색으로 칠해지므로 이분 그래프가 될 수 없다.
'''
def isBinaryGraph(start):
    queue = deque([start])
    visited[start] = 1 # 1번 노드는 색상코드 1로 기입
    while queue:
        node = queue.popleft()
        # 인접 노드에 대해서 탐색 수행
        for iNode in graph[node]:
            # 인접 노드 중에 방문했던 적이 있는 노드가 존재한다면 현재 자신의 색과 같은지 비교
            if visited[iNode] != 0:
                if visited[iNode] == visited[node]:
                    # 현재 자신의 색과 같다 -> 인접 노드가 같은 색으로 칠해졌다 -> 이분그래프 불가능
                    return False
            else: # 방문한 적이 없는 노드에 대해
                visited[iNode] = -(visited[node]) # 현재 색과 다른 색상으로 기록
                queue.append(iNode)
    return True # 문제없이 끝까지 수행하였다면 이분그래프가 가능함

for _ in range(int(input())):
    v,e = map(int,input().split())
    visited = [0]*(v+1) # 0방문한적 없는 노드, 1,-1은 색상
    graph = [[] for _ in range(v+1)]
    for _ in range(e):
        a,b = map(int,input().split())
        graph[a].append(b)
        graph[b].append(a)
        
    # 그래프가 모두 연결되어있지 않을 수 있음..?
    isBinary = True
    for v in range(1, v+1):
        if visited[v] == 0:  # 방문하지 않은 노드에서 BFS 시작
            if not isBinaryGraph(v):
                isBinary = False
                break

    print("YES" if isBinary else "NO")

+ Recent posts