[브루트 포스]

 

1018: 체스판 다시 칠하기

더보기
from sys import stdin
input = stdin.readline

n, m = map(int, input().split())

chessTable = []
for i in range(n):
  s = input().rstrip()
  chessTable.append(list(s))
  # list(map(~~)) 이대로 입력 받으니까 문자열 자체가 한개의 요소라서 개별문자 접근이 안됨

def paint_chessTable(arr, y, x):
  
  s1 = "WBWBWBWB" # 번갈아가면서 확인해야함
  s2 = "BWBWBWBW"
  cnt = 0
  for i in range(y, y + 8):
    idx = 0
    for j in range(x, x + 8):
      if i % 2 == 0:
        if arr[i][j] != s1[idx]:
          cnt += 1
      else:
        if arr[i][j] != s2[idx]:
          cnt += 1
      idx += 1

  return min(cnt, 64-cnt) # 반대 경우의 수 : 최대 경우의 수(64) - 현재 경우의 수

answer = n * m # 최악의 경우에도 32는 넘지 않지만 대충 크게 넣음

for i in range(n):
  for j in range(m):
    # 인덱스가 넘지않는 범위 내의 8x8
    if i + 8 <= n and j + 8 <= m:
      answer = min(answer, paint_chessTable(chessTable, i, j))

print(answer)

구현하는데 시간이 너무 오래걸림

 

[자료구조]

 

1021: 회전하는 큐

더보기
#include <iostream>
#include <deque>
#include <algorithm>

using namespace std;

int main(void)
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

	int n, m;
	int answer = 0;

	deque<int> rq;
	deque<int> nums;

	cin >> n >> m;

	for (int i = 0; i < m; ++i)
	{
		int data;
		cin >> data;
		nums.push_back(data);
	}

	for (int i = 1; i <= n; ++i)
	{
		rq.push_back(i);
	}

	for (auto& num : nums)
	{
		int idx = find(rq.begin(), rq.end(), num) - rq.begin();
		while (num != rq.front())
		{
			// 왼쪽으로 밀기
			if (idx < (int)rq.size() - idx)
			{
				rq.push_back(rq.front());
				rq.pop_front();
			}
			else
			{
				rq.push_front(rq.back());
				rq.pop_back();
			}
			answer++;
		}
		rq.pop_front();
	}

	cout << answer;

	return 0;
}

 

1269: 대칭 차집합

더보기
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

	int n, m, data, v_size;
	vector<int> v1, v2;

	cin >> n >> m;

	v_size = n > m ? n : m;

	while (n--)
	{
		cin >> data;
		v1.push_back(data);
	}	
	while (m--)
	{
		cin >> data;
		v2.push_back(data);
	}

	sort(v1.begin(), v1.end());
	sort(v2.begin(), v2.end());

	vector<int> v3(v_size);

	// find에서 begin을 빼주면 index를 구할수 있는걸 응용함
	// 두 집합 결과의 다음 iterator가 반환되기때문에 begin을 빼주면 원소의 개수가 된다
	auto ans1 = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin()) - v3.begin();
	auto ans2 = set_difference(v2.begin(), v2.end(), v1.begin(), v1.end(), v3.begin()) - v3.begin();

	cout << ans1 + ans2 << '\n';

	return 0;
}

 

다른사람 풀이(46781878)는 이진 탐색으로 교집합 되는 원소의 개수(C)를 구한 뒤 N+M-(2*C)로 정답처리를 받았다.

 

 

[정렬]

 

2108: 통계학

더보기
from sys import stdin
from collections import Counter
input = stdin.readline

n = int(input())

arr = []
arr_sum = 0
for i in range(n):
  data = int(input())
  arr_sum += data
  arr.append(data)

arr.sort()
d = sorted(dict(Counter(arr)).items(), key=lambda x:(-x[1],x[0]))
# 더 심플하게 수정가능. d = Counter(arr).most_common(2)

print(round(arr_sum/n)) # 산술 평균
print(arr[n//2]) # 중앙값
if d[0][1] == d[1%n][1]: # 최빈값
  print(d[1%n][0])
else:
  print(d[0][0])
print(abs(arr[0] - arr[-1])) # 최대-최소 차이

Counter 처음써봤다. 다른사람 소스 보고 most_common의 존재를 알아서 다행임

 

1427: 소트인사이드

더보기
from sys import stdin
input = stdin.readline

# 문자열로 받아서
n = input().rstrip()

arr = []
for i in range(len(n)):
  # 숫자로 쪼개 넣은뒤
  arr.append(int(n[i]))

# 내림차순 정렬
arr.sort(reverse=True)

for i in arr:
  print(i, end='')

 

18870: 좌표 압축

더보기
#include <iostream>
#include <vector>
#include <algorithm>

#define horizon first
#define index second

using namespace std;

bool cmp_horizon(pair<int, int>& v1, pair<int, int>& v2)
{
	return v1.horizon < v2.horizon;
}

bool cmp_index(pair<int, int>& v1, pair<int, int>& v2)
{
	return v1.index < v2.index;
}

int main(void)
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

	vector<pair<int, int>> coordinates;
	int n;

	cin >> n;

	for (int i = 0; i < n; ++i)
	{
		int t;
		cin >> t;
		coordinates.push_back({ t, i });
	}

	sort(coordinates.begin(), coordinates.end(), cmp_horizon);
    // [](auto& v1, auto& v2) { return v1.horizon < v2.horizon; }); 람다식 가능

	int idx = 0;

	for (int i = 0; i < n - 1; ++i)
	{
		if (coordinates[i].horizon == coordinates[i + 1].horizon)
		{
			coordinates[i].horizon = idx;
		}
		else
		{
			coordinates[i].horizon = idx++;
		}
	}
	coordinates[n - 1].horizon = idx;

	sort(coordinates.begin(), coordinates.end(), cmp_index);
    // [](auto& v1, auto& v2) { return v1.index < v2.index; }); 람다식 가능

	for (auto& data : coordinates)
	{
		cout << data.horizon << " ";
	}

	return 0;
}

 

cmp_horizon, cmp_index 대신에 람다식을 넣어서 더 짧게 할수도 있다.

다른사람 풀이(46641944) 중에 페어를 쓰지 않고 unique와 lower_bound(=bisect_left)를 이용해서 간결하게 풀어낸 해답이 있다.

 

1764: 듣보잡

더보기
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
	ios_base::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

	vector<string> cant_hear;
	vector<string> cant_see;
	int n, m;
	string s;

	cin >> n >> m;

	while (n--)
	{
		cin >> s;
		cant_hear.push_back(s);
	}
	while (m--)
	{
		cin >> s;
		cant_see.push_back(s);
	}

	// intersection 수행 전 모두 정렬이 되어있어야 함
	sort(cant_hear.begin(), cant_hear.end());
	sort(cant_see.begin(), cant_see.end());

	vector<string> cant_hear_see(cant_hear.size() + cant_see.size());

	// 두 집합 결과의 end iterator가 반환됨
	auto iter = set_intersection(cant_hear.begin(), cant_hear.end(),
    			cant_see.begin(), cant_see.end(), cant_hear_see.begin());
	// 해당 iterator부터 새 컨테이너의 end까지 삭제를 해주면 잉여 크기가 삭제된다
    cant_hear_see.erase(iter, cant_hear_see.end());

	cout << cant_hear_see.size() << '\n';
	for (auto& item : cant_hear_see)
	{
		cout << item << '\n';
	}

	return 0;
}

 

set의 find 또는 algorithm의 find를 이용하면 intersection을 사용하지 않으므로 컨테이너를 추가로 쓰지 않아도 돼서 메모리를 아낄 수 있다. 코드 가독성도 더 좋아질 것 같다.

 

[수학]

 

9020: 골드바흐의 추측

더보기
from sys import stdin
input = stdin.readline

primeNumber = [1] * 10001

# 에라토스테네스의 체
# n의 최대약수는 sqrt(n) 이하이므로 제곱근+1 까지만 루프를 돈다
for i in range(2, int(10000**0.5)+1):
  if primeNumber[i] == 1:
    for j in range(i*2, 10001, i):
      primeNumber[j] = 0

def goldbach_partition(n):
  # (n/2-a) + (n/2+a) = n
  for i in range(n//2, 0, -1):
    # 차이가 가장 적은 소수판별
    if primeNumber[i] == 1 and primeNumber[n - i] == 1:
      print(i, n-i)
      return
  
for tc in range(int(input())):
  goldbach_partition(int(input()))

 

처음엔 이중 for문으로 썼다가 당연히 시간초과가 났다. 머리 싸매고 고민해도 답이 안나오길래 질문 게시판에서 힌트란 힌트는 다 훑어보고 난 뒤에서야 수정했다.

어떤 짝수를 두 소수의 합으로 나타낼 수 있다는거 이제 잊고싶어도 못잊을거같다.

 

 

[그리디]

 

1931: 회의실 배정

더보기
n = int(input())

array = [0] * n

for i in range(n):
  array[i] = tuple(map(int, input().split()))

array.sort(key = lambda x: (x[1], x[0]))

end, result = -1, 0

for i in range(n):
  # 현재 진행중인 회의의 종료시간보다 뒤에 있으면서 end가 짧은거 하나만 하면 됨
  # 두가지 조건을 만족해야 하지만 이미 end가 짧은순으로 정렬되어 있기때문에
  # 종료시간 뒤에 있는 가장 가까운 값을 꺼내오기만 하면 end가 짧은게 보장됨
  if array[i][0] >= end:
    end = array[i][1] # 종료시간 갱신
    result += 1

print(result)

얼마전에 공부 했던 내용인데 최적해를 구하는게 한끗차이로 안떠올라서 시간을 좀 잡아먹었다

코드업 4713 이랑 동일한 문제라고 생각해서 코드를 길게 짜다가 조건자체가 더 심플해서 간소화시켰다

근데 지금 생각해보니 코드업 4713도 간소화 시킬수 있을것같은데 일단 보류함

 

1946: 신입 사원

더보기
from sys import stdin

t = int(stdin.readline())

for ft in range(t):
  n = int(stdin.readline())
  arr = [0] * n
  
  for i in range(n):
    arr[i] = tuple(map(int, stdin.readline().split()))

  # 첫번째 값 기준으로 오름차순 정렬
  arr.sort() 

  # 1등의 두번째 값을 기준으로 잡고 1등은 무조건 통과이므로 1부터 시작 
  result = 1
  pivot = arr[0][1]
  
  for i in range(1, n):
    if arr[i][1] < pivot:
      result += 1
      pivot = arr[i][1]
      
  print(result)

난이도가 높지 않은 문제는 최적해 구하는 방법에 접근은 잘 하는데 시간이 너무 오래걸린다

그리고 구현할 때 더 단순한 방법이 있음에도 자꾸 어렵게 함

 

 

11501: 주식

더보기

 

# n일보다 n+1일이 크거나 같으면 구매함
# n일보다 n+1이 작으면 판매함

from sys import stdin

t = int(stdin.readline())

for ft in range(t):
  n = int(stdin.readline())
  arr = [x for x in list(map(int, stdin.readline().split()))]

  stock = 0
  result = 0
  highest = max(arr)
  
  for i in range(n-1):
    if arr[i] <= arr[i+1]:
      stock += 1
      result -= arr[i]
    # 다음날 가격이 떨어지지만 최고가를 만나지 않았으면 팔지 않음
    elif arr[i] > arr[i+1] and arr[i] >= highest:
      result += (arr[i] * stock)
      highest = max(arr[i+1:]) # 최고가 재설정
      stock = 0
    # 전일 등락과 상관없이 최고가를 만나기 전까지는 매수
    else:
      stock += 1
      result -= arr[i]
  # 루프를 한번 덜돌기때문에 마지막날에 대한 결과 처리
  result += (highest * stock)

  print(result)

시간이 9초가 나왔다... 루프 자체는 O(N)인데 아무래도 max를 부르는 횟수가 많아지면 기하급수적으로 늘어나는것 같음

처음 제출했을때 틀리길래 반례를 찾아서 다시 조립했다. 최고가 갱신쪽이 문제였다

2초대가 나온 다른사람 코드를 봤는데 심플했다. 읽어보니 접근 방법은 이해했는데 그런 코드를 작성할 수 있다는게 신기함

 

4796: 캠핑

더보기
count = 1

while True:
  l, p, v = map(int, input().split())

  if l + p + v == 0:
    break
    
  answer = 0
  
  while v >= l:
    v -= p
    answer += l
    
  if v > 0:
    answer += v

  print("Case "+str(count)+": "+str(answer))
  count += 1

코드 짧게 짜놔서 뿌듯했는데 자꾸 오답나길래 보니까 예제출력이랑 동일하게 하는 부분을 처리 안해서 그랬었음

 

[BFS]

'알고리즘 > 문제풀이' 카테고리의 다른 글

코드업 문제 풀이  (0) 2022.07.15
이코테 문제들 힌트와 풀이  (0) 2022.07.14

[시뮬레이션]

 

6098: [기초-리스트] 성실한 개미

더보기
# 0: 갈수있는 곳 1: 벽 2: 먹이
# 계속 오른쪽으로만 가는데 벽이 있으면 한칸 아래로 감

maze = []
x, y = 1, 1
for i in range(10):
  maze.append(list(map(int, input().split())))

dx = (0, 1)
dy = (1, 0)
turn_count = 0

# 첫 위치는 방문처리
maze[x][y] = 9

while True:
  # 회전을 두번 했다면 종료
  if turn_count >= 2:
    break
  
  # 이동할 곳이 벽이 아니라면
  if maze[x+dx[turn_count]][y+dy[turn_count]] == 0:
    x = x + dx[turn_count]
    y = y + dy[turn_count]
    maze[x][y] = 9
    turn_count = 0
  # 이동할 곳이 벽이라면
  elif maze[x+dx[turn_count]][y+dy[turn_count]] == 1:
    turn_count += 1
  # 그 외의 경우는 먹이를 발견했을 때
  else:
    x = x + dx[turn_count]
    y = y + dy[turn_count]
    maze[x][y] = 9
    break

for i in range(10):
  for j in range(10):
    print(maze[i][j], end=' ')
  print("")

 

 

[그리디]

 

3120: 리모컨

더보기
a, b = map(int, input().split())
target = abs(a - b)

# 먼저 10으로 나눠준다
result = target // 10
target = target % 10


if target in [1, 5]: # 1: +1 / 5: +5
  result += 1
elif target in [2, 4, 6, 9]: # 2: +1,+1 / 4: 5,-1 / 6: 5,+1 / 9: +10,-1
  result += 2
elif target in [3, 7, 8]: # 3: +1,+1,+1 / 7: +5,+1,+1 / 8: +10,-1,-1
  result += 3

print(result)

쓸데없이 생각을 너무 복잡하고 길게 하는것 같다. 최적해를 찾기 위해 생각을 단순화 시킬 필요가 있다

고작 이거하나 짜는데 1시간이 걸렸다

 

3321: 최고의 피자

더보기
t = int(input())
dPrice, tPrice = map(int, input().split())
dCalorie = int(input())

tCalorie = []
for i in range(t):
  tCalorie.append(int(input()))
  
# cpd의 효율을 위해 내림차순 정렬
tCalorie.sort(reverse=True)

totalCalorie = dCalorie
totalPrice = dPrice
cpd = totalCalorie // totalPrice # calorie per dollar

for x in tCalorie:
  # 토핑의 cpd가 전체 cpd보다 높으면 토핑 추가
  if (x // tPrice) > cpd:
    totalCalorie += x
    totalPrice += tPrice
    cpd = totalCalorie // totalPrice
  # 오름차순 정렬이기때문에 한번만 만족하지 못해도 루프 탈출
  else:
    break

print(format(cpd,".0f"))

접근은 거의 다 했는데 누적 평균값을 고려하지 않아서 조금 헤맸음

 

4040: 펜션

더보기
from sys import stdin

period, r = map(int, stdin.readline().split())

schedule = [0] * period

for i in range(0, period):
  schedule[i] = (list(map(str, stdin.readline().rstrip())))

start, end = map(int, stdin.readline().split())
start -= 1 # 인덱스 접근을 편하게 하기위해 감소
end -= 1
changeCount = -1

while start < end:
  maxDay = 0
  
  for i in range(r):
    count = 0
    for j in range(start, end):
      # 빈방을 발견하면 계속 확인함
      if schedule[j][i] in 'O':
        count += 1
      else:
        break
    # 최대 숙박 가능일
    if maxDay < count:
      maxDay = count

  # 숙박 가능일이 0이 아니면 방이 있음
  if maxDay != 0:
    start += maxDay
    changeCount += 1
  # 숙박 가능일이 0이면 예약이 다 찬상태
  else:
    changeCount = -1
    break

print(changeCount)

시간초과가 계속 뜨길래 입력방법도 바꿔보고 했다가 뒤늦게 안 사실은 마지막에 break 하나를 안넣어서 루프탈출을 못해서 그런거였다. 그걸 못찾고 계속 코드 다듬다가 30분이 갔다

이번에는 충분히 할 수 있는데도 '이 방법은 아니지 않을까?' 하면서 고민을 너무 오래했다

최대 숙박 가능일들을 뽑아서 방 번호와 튜플로 묶어서 리스트에 보관하는 뻘짓을 했었는데 조금 생각해보니 그런 데이터를 보관할 필요가 없다는걸 깨닫고 다 날려버림

 

4713: 공주님의 정원

더보기
# [0]시작월 [1]시작일 [2]종료월 [3]종료일

# 3/1 ~ 11/30
# 1. 시작일(초기3/1) 이하 꽃중에 가장 늦게까지 피는 꽃 선택
# 2. 해당 꽃 종료일 이하 꽃중에 현재 종료일보다 더 뒤쪽으로 가장 늦게까지 피는 꽃 선택
# 만약 선택할 수 없다면 0 반환후 즉시 종료
# 매 선택마다 11/30이 초과되는지 확인해야한다
# 선택된 꽃 카운팅 해서 출력

n = int(input())

array = [0] * n
for i in range(n):
  array[i] = list(map(int, input().split()))

# 시작월일 기준으로 정렬
array.sort(key = lambda x: (x[0], x[1]))

index, result = 0, 0
start = [3, 1]
end = [11, 30]

longFlower = array[0]
while True:
  count = 0
  for i in range(index, n):
    # 시작일보다 아래에 있는 꽃 탐색
    if (array[i][0] < start[0]) or (array[i][0] <= start[0] and array[i][1] <= start[1]):
      # 가장 늦게까지 피는 꽃 탐색
      longFlower = max(longFlower, array[i], key = lambda x: (x[2], x[3]))
      count += 1
      index = i + 1 # 루프 횟수를 줄이기 위한 변수
    else:
      break

  # 시작 날짜를 꽃의 끝나는 날로 재지정
  start[0], start[1] = longFlower[2], longFlower[3]
  result += 1

  # 조건을 만족한 경우 루프 탈출
  if start[0] > end[0]:
      break
  # 조건을 하나도 찾지 못한경우 경우의 수가 없음
  elif count == 0:
    result = 0
    break

print(result)

220719 solved

뭔가.. 뭔가 좀 깔끔하지 않은 것 같은데 아무튼 풀었음

처음에 주석으로 워크플로우 대충 구상해놓고 하니까 수월했음

 

[BFS/DFS]

 

2102: 배수(Hard)

더보기
from collections import deque

n = int(input())

def bfs(pivot, start):
  queue = deque([start])

  while queue:
    val = queue.popleft()

    # 최대 범위보다 크면 루프 종료
    if val >= 2**64:
      return 0
    # 나누어지면 공배수라는 뜻
    elif val % pivot == 0:
      return val

    # 큐에 값 추가. 그림으로 그리면 이진트리와 비슷한 그림이 그려짐
    queue.append(val * 10)
    queue.append(val * 10 + 1)

print(bfs(n, 1))

220716 solved

BFS 이용. 이상하게 문제 이해가 오래걸려서 풀이를 좀 찾아본 뒤에서야 이해했음

 

2605: 캔디팡

더보기
# 2605 캔디팡 220715

width, height = 7, 7

graph = []
for i in range(height):
  graph.append(list(map(int, input().split())))

# 7x7 리스트 초기화
visited = [[False] * width for _ in range(height)]

count = 0

def dfs(x, y, color):
  global count
  
  if x <= -1 or x >= width or y <= -1 or y >= height:
    return False

  # 방문하지 않음과 동시에 같은 색상인것만 골라서 방문처리
  if visited[x][y] == False and graph[x][y] == color:
    visited[x][y] = True
    count += 1
    dfs(x+1, y, color)
    dfs(x-1, y, color)
    dfs(x, y+1, color)
    dfs(x, y-1, color)
    return True

  return False

result = 0

for i in range(width):
  for j in range(height):
    dfs(i, j, graph[i][j])
    # 3개 이상의 색이 연달아 있는 경우만 카운팅
    if count >= 3:
      result += 1
    count = 0

print(result)

220715 solved

DFS 이용

 

2610: 그림판 채우기

더보기
# _: 색칠되지 않은 부분, *: 색칠된 부분

paint = []
for i in range(10):
  paint.append(list(input()))

x, y = map(int, input().split())

def dfs(array, _x, _y):
  if _x <= -1 or _x >= 10 or _y <= -1 or _y >= 10:
    return

  # 보통 인지하는 x,y와 배열의 행렬은 반대이므로 바꿔서 사용
  if array[_y][_x] == '_':
    array[_y][_x] = '*'
    dfs(array, _x+1, _y)
    dfs(array, _x-1, _y)
    dfs(array, _x, _y+1)
    dfs(array, _x, _y-1)
  else:
    return

dfs(paint, x, y)

for i in range(10):
  for j in range(10):
    print(paint[i][j], end='')
  print("")

220716 solved

일반적인 DFS. visited 처리를 따로 안해도 됨

 

3212: 위상정렬

더보기
from queue import PriorityQueue

v, n = map(int, input().split())

graph = [[0] for _ in range(v+1)]
indegree = [0] * (v+1)
p_queue = PriorityQueue()

for i in range(n):
  x, y = input().split()
  x, y = int(x), int(y)
  graph[x].append(y)
  indegree[y] += 1

for i in range(1, v+1):
  if indegree[i] == 0:
    p_queue.put(-i)

while not p_queue.empty():
  here = -p_queue.get()
  print(here)

  for i in graph[here]:
    indegree[i] -= 1
    if indegree[i] == 0:
      p_queue.put(-i)

정답은 이건데 솔직히 봐도 아직 모르겠음

 

 

 

삽질의 기록들

# v: 정점의 개수 n: 간선의 개수. 인덱스는 둘다 1부터 쓸것

from queue import PriorityQueue

v, n = map(int, input().split())

# 열n+1 행v+1. 행 인덱스 0은 버릴거기때문에 v만 한개 더 크게
# 앞쪽이 가로길이(열/간선), 뒤쪽이 세로길이(행/정점)
graph = [[0] for _ in range(v+1)]

# 탐색 시작 노드를 저장할 변수
init_v = 0

for i in range(n):
  x, y = input().split()  
  graph[int(x)].append(int(y))
  if i == 0:
    init_v = int(x)
    
result = []

def dfs(array, start, visited):
  # 우선순위 큐 사용 선언
  p_queue = PriorityQueue()
  p_queue.put(start)
  visited[start] = True

  # 큐가 빌때까지
  while not p_queue.empty():
    v = p_queue.get() 
    # 우선순위 큐를 통해 나온 값은 언제나 최소값과 중복이 아님을 보장받는다
    result.append(v)

    for i in range(1, len(array[v])):
      if not visited[array[v][i]]:
        p_queue.put(array[v][i])
        visited[array[v][i]] = True
        
  return

# 방문을 체크하는 리스트
visitedCheck = [False] * (v+1)

dfs(graph, init_v, visitedCheck)

# 주어진 노드 개수만큼 들어가지 않았다면 탐색을 실패했다는 소리
# 애초에 중복은 우선순위 큐에 들어가지 않으니 순회인지, 끊긴건지는 알 수 없음
if len(result) <= v-1:
  print(-1)
else:
  for i in range(len(result)):
    print(result[i])

반례를 보니 우선순위 큐로 풀리네? 해서 짜놨더니 막히고 다음 반례를 확인하니까 아니었음

여전히 헤메고 있는중

 

 

 

'알고리즘 > 문제풀이' 카테고리의 다른 글

백준(BOJ) 문제 풀이  (0) 2022.07.20
이코테 문제들 힌트와 풀이  (0) 2022.07.14

[그리디]

 

- 큰 수의 법칙

큰 수 두개만 골라서 쓰면 된다

 

- 숫자 카드 게임

각 행마다 가장 작은 수를 찾고 그 수 중에서 가장 큰 수를 찾는 것

 

- 1이 될 때까지

최대한 많이 나누기

1을 빼는 작업은 몫에 k를 곱함으로써 한번에 수행할 수 있다

더보기
n, k = map(int, input().split())
count = 0

while n!=1:
  if n%k==0:
    n = n//k
    count += 1
  else:
    n -= 1
    count += 1

print(count)

이코테 책에서는 이보다 더 빠른 방법을 사용했다

 

- 곱하기 혹은 더하기

대부분 곱하는게 이득이지만 두 수중 0 또는 1인 경우는 더하는게 이득이다.

더보기
data = input()

result = int(data[0])

for i in range(1, len(data)):
  num = int(data[i])
  if result <= 1 or num <= 1:
    result += num
  else:
    result *= num

print(result)

 

- 모험가 길드

정렬 후 낮은 공포도부터 그룹 결성을 시키면 된다

더보기
n = int(input())
data = list(map(int, input().split()))
data.sort()

result = 0
f = 0.0

# 인원수가 가장 낮은 공포도보다 적으면 안됨
while len(data) > data[0]:
  f += 1.0/float(data[0]) # 1/공포도 만큼을 인원수만큼 더해서 1이 넘으면 그룹 결성
  data.pop(0) # 인원을 빼줌
  if f >= 1.0:
    result += 1
    f = 0.0

print(result)

개념 접근 자체는 이코테의 해답과 비슷한데 좀 더 복잡하게 생각했기때문에 단순화가 필요함

연산도 필요없고 그저 카운팅만 시키면 되는 단순한 해답이 있음

 

[구현]

 

- 상하좌우

기준점을 두고 Vector이동이라고 가정하면 구현하기 쉽다

더보기
dx = [0, 0, -1, 1]
dy = [-1, 1, 0, 0]
move_types = ['L', 'R', 'U', 'D']
x, y = 1, 1

n = int(input())
plans = list(map(str, input().split()))

for plan in plans:
  for i in range(len(move_types)):
    if plan == move_types[i]:
      nx = x + dx[i] # 반복문 안에서 선언된 변수도 외부에서 사용할 수 있는듯 하다
      ny = y + dy[i] # c/c++이랑 다른 문법인것 같음

  if nx < 1 or ny < 1 or nx > n or ny > n:
    continue
  x, y = nx, ny

print(x, y)

 

- 시각

범위가 하루로 잡혀있고 하루는 86400초이기 때문에 복잡하게 생각할 것 없이 단순하게 모든 초를 조사하면 된다

더보기
n = int(input())
three = 15 # 0~60 사이 3이 포함된 숫자의 개수
result = 0

for i in range(n+1):
  if i==3 or i==13 or i==23: # 시간에 3이 포함되면 1시간 전체가 포함됨 
    result += 3600
  else: # 분에 3이 포함되면 60초가 모두 포함되어야 하고 그 외엔 초에 포함된 3의 경우만 고려하면 됨
    result += three * 60 + (60-three) * three

print(result)

평소처럼 단순하게 생각했으면 더 쉽게 풀수 있었을텐데 경우의 수를 계산하느라 시간제한 15분이 넘어버렸다

 

- 왕실의 나이트

8X8로 제한이 되어있으니 완전 탐색으로 구현하면 됨

더보기
# 행row 위치는 1-8, 열column 위치는 a-h
# 따로 주석 달게 없음

# 리스트 이용
steps = { (2, -1), (2, 1), (1, 2), (-1, 2), (-2, -1), (-2, 1), (-1, -2), (1, -2) }

data = input()
row = int(data[1])
column = int(ord(data[0])) - int(ord('a')) + 1
result = 0

for step in steps:
  nrow = row + step[0]
  ncolumn = column + step[1]

  if nrow < 1 or ncolumn < 1 or nrow > 8 or ncolumn > 8:
    continue
  result += 1

print(result)

 

- 문자열 재정렬

완전 탐색. 알파벳과 숫자를 분리한 후 차후 합하면 됨

더보기
s = list(input())
s.sort() # 리스트로 입력 받고 정렬
result = 0

while True:
  # 오름차순 정렬 시 맨 앞에는 숫자가 오므로 숫자인지 판별
  if s[0].isdigit(): 
    result += int(s[0])
    # 다음 문자 확인을 위해 리스트에서 제거
    s.remove(s[0])
  else:
    break

print(''.join(s)+str(result))

 

이코테에서는 입력받은 문자열을 모두 순회하면서 문자는 리스트에, 숫자는 합산해서 따로 관리 후 마지막에 숫자 합을 리스트에 추가시켜서 출력함

다만 이런경우는 문자열 관련 기능이 강력한 파이썬에서나 가능한거고 c/c++ 환경에서는 완전 탐색으로 구현해야 할 것 같다

 

- 게임 개발

문제 이해하는게 가장 어려움. dx, dy라는 별도의 벡터를 이용하여 구현하면 어렵지 않음

더보기
# # 0:북 1:동 2:남 3:서
# # 0:육지 1:바다

n, m = map(int, input().split())
x, y, direction = map(int, input().split())

# 이동 벡터
dx = [0, 1, 0, -1]
dy = [-1, 0, 1, 0]

d = [[0] * m for _ in range(n)]
d[x][y] = 1 # 첫 위치는 방문한걸로 기록

map_list = []
for i in range(n):
  map_list.append(list(map(int, input().split())))

def turn_left():
  global direction
  direction -= 1
  
  if direction < 0:
    direction = 3

turn_time = 0
count = 1

while True:
  turn_left()
  
  nx = x + dx[direction]
  ny = y + dy[direction]

  # 방문한적이 있거나 바다인 경우
  if d[nx][ny] == 1 or map_list[nx][ny] == 1:
    turn_time += 1
  else:
    d[nx][ny] = 1
    x = nx
    y = ny
    count += 1
    turn_time = 0
    continue

  # 4번의 회전을 모두 한 경우
  if turn_time == 4:
    nx = x - dx[direction]
    ny = y - dy[direction]

    # 뒤가 바다라면 시뮬레이션 종료
    if map_list[nx][ny] == 1:
      break
    else:
      x = nx
      y = ny
    turn_time = 0

print(count)

 

[DFS/BFS]

 

- 음료수 얼려 먹기

주어진 노드들을 따로 관리할 필요가 없으므로 visited를 별도로 관리하지 않아도 되고 DFS로 구현하면 된다

더보기
# N x M 얼음틀
# 구멍이 뚫린 부분은 0, 칸막이가 존재하는 부분은 1
# 구멍이 뚫린 부분끼리 상, 하, 좌, 우가 붙어있는 경우 서로 붙은것으로 간주

n, m = map(int, input().split())

graph = []
for i in range(n):
  graph.append(list(map(int, input())))

def dfs(x, y):
  # 범위를 벗어나는 경우 즉시 종료
  if x <= -1 or x >= n or y <= -1 or y >= m:
    return False

  # 아직 방문하지 않은 곳일 때
  if graph[x][y] == 0:
    # 방문처리 후 상하좌우 모두 탐색
    graph[x][y] = 1
    dfs(x+1, y)
    dfs(x-1, y)
    dfs(x, y+1)
    dfs(x, y-1)
    return True
  return False

count = 0
# 모든 노드에 대하여 조사
for i in range(n):
  for j in range(m):
    if dfs(i, j) == True:
      count += 1

print(count)

 

- 미로 탈출

노드간의 간선 값이 모두 1이므로 목표지점까지의 최단거리를 보장할 수 있다. BFS로 구현

더보기
# 미로의 크기는 NxM이고 출구는 좌표는 (N, M)

from collections import deque

n, m = map(int, input().split())
# 상, 하, 좌, 우
dx = (-1, 1, 0, 0)
dy = (0, 0, -1, 1)

maze = []
for i in range(n):
  maze.append(list(map(int, input())))

def bfs(x, y):
  queue = deque()
  # 최초 시작노드 큐에 삽입
  queue.append((x, y))

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

    # 각 노드에 연결된 최대 4방향에 대해 확인
    for i in range(4):
      nx = x + dx[i]
      ny = y + dy[i]
      
      if nx <= -1 or nx >= n or ny <= -1 or ny >= m:
        continue
      
      if maze[nx][ny] == 0:
        continue

      if maze[nx][ny] == 1:
        maze[nx][ny] = maze[x][y] + 1
        queue.append((nx, ny))

  return maze[n-1][m-1]

print(bfs(0, 0))

 

[정렬]

 

- 위에서 아래로

내림차순 정렬만 하면 된다

더보기
n = int(input())

arr = []
for i in range(n):
  arr.append(int(input()))

# 내림차순 정렬
arr.sort(reverse=True)

for i in range(n):
  print(arr[i], end=' ')

 

- 두 배열의 원소 교체

오름차순, 내림차순 정렬 후에 값 교체해주고 더하면 된다

더보기
# 두 배열을 각각 오름차순, 내림차순 정렬 후 k만큼 앞에서 부터 바꿔주면 됨

n, k = map(int, input().split())

aArr = list(map(int, input().split()))
bArr = list(map(int, input().split()))

aArr.sort() # 오름차순
bArr.sort(reverse=True) # 내림차순

for i in range(k):
  if aArr[i] < bArr[i]: # a보다 b가 큰 경우 서로 값을 바꿔줌
    aArr[i], bArr[i] = bArr[i], aArr[i]
  # 더이상 a보다 큰 수는 없으면 루프 탈출
  else:
    break

print(sum(aArr))

 

[이진 탐색]

 

- 떡볶이 떡 만들기

적절한 높이를 찾을 때까지 이진 탐색을 수행하여 높이를 반복 조정하면 됨

더보기
n, m = map(int, input().split())
hlist = list(map(int, input().split()))

start = 0
end = max(hlist)

result = 0
while start <= end:
  total = 0
  mid = (start + end) // 2

  for x in hlist:
    # 잘랐을 때 떡의 양 계산
    if x > mid:
      total += x - mid

  # 떡의 양이 작으면 자르는 높이를 줄인다
  if total < m:
    end = mid - 1
  # 떡의 양이 많으면 자르는 높이를 늘린다
  # 최대한 덜 자르는게 목표이므로 이때 결과값을 보관해둔다
  else:
    start = mid + 1
    result = mid

print(result)

 

이 문제의 경우 이진 탐색을 이용한다고 해서 데이터의 정렬이 반드시 필요한 것은 아니다

찾아야 하는 해가 계속 바뀌기 때문에 탐색 과정에 이진 탐색의 개념만 도입하여 파라메트릭 서치로 바꾼것이다

 

- 정렬된 배열에서 특정 수의 개수 구하기

bisect(C++에서는 lower_bound, upper_bound) 사용시 매우 쉽게 구할 수 있다

더보기
def bisect_left(array, x):
  start = 0
  end = len(array) - 1
  result = 0
  
  while start <= end:
    mid = (start + end) // 2

    # 값을 찾았다면 루프 종료가 아니라 mid 값을 보관하고 중복된 값을 찾으러 범위를 좁혀준다
    # 왼쪽을 찾아야 하므로 끝점을 땡겨준다
    if array[mid] == x:
      result = mid
      end = mid - 1
    elif array[mid] > x:
      end = mid - 1
    else:
      start = mid + 1

  # 값 삽입시 현재 인덱스의 위치에 삽입하고 밀어내면 된다
  return result

def bisect_right(array, x):
  start = 0
  end = len(array) - 1
  # 마지막에 1을 더해주기 때문에 0을 만들어주기 위해 -1 대입
  result = -1
  
  while start <= end:
    mid = (start + end) // 2
    
    # 값을 찾았다면 루프 종료가 아니라 mid 값을 보관하고 중복된 값을 찾으러 범위를 좁혀준다
    # 오른쪽을 찾아야 하므로 시작점을 끌어올려준다
    if array[mid] == x:
      result = mid
      start = mid + 1
    elif array[mid] > x:
      end = mid - 1
    else:
      start = mid + 1

  # 값 삽입시 현재 인덱스보다 오른쪽에 삽입해야 하기때문에 1을 더해준다
  return result + 1

n, x = map(int, input().split())
array = list(map(int, input().split()))

result = bisect_right(array, x) - bisect_left(array, x)

if result == 0:
  print(-1)
else:
  print(result)

 

아래처럼 bisect 라이브러리를 이용해서 쉽고 간단하게 구현할 수 있지만 연습을 위해 직접 구현

실제 bisect는 값을 찾지 못하면 정렬이 깨지지 않는 적절한 인덱스를 반환한다

그것에 맞춰서 수정한건 아래와 같다

 

def bisect_left(array, x):
  start = 0
  end = len(array) - 1
  # 찾는 값이 리스트 안의 모든 값보다 클때 result값이 변하는 일이 없으므로 미리 삽입될 끝 인덱스 지정
  result = len(array)
  
  while start <= end:
    mid = (start + end) // 2

    # 값을 찾았다면 루프 종료가 아니라 mid 값을 보관하고 중복된 값을 찾으러 범위를 좁혀준다
    # 왼쪽을 찾아야 하므로 끝점을 땡겨준다
    # 값을 찾지 못한경우 적절한 위치를 찾기 위해 부등호 >= 를 사용해준다
    if array[mid] >= x:
      result = mid
      end = mid - 1
    elif array[mid] > x:
      end = mid - 1
    else:
      start = mid + 1

  # 값 삽입시 현재 인덱스의 위치에 삽입하고 밀어내면 된다
  return result

def bisect_right(array, x):
  start = 0
  end = len(array) - 1
  # 마지막에 1을 더해주기 때문에 0을 만들어주기 위해 -1 대입
  result = -1
  
  while start <= end:
    mid = (start + end) // 2
    
    # 값을 찾았다면 루프 종료가 아니라 mid 값을 보관하고 중복된 값을 찾으러 범위를 좁혀준다
    # 오른쪽을 찾아야 하므로 시작점을 끌어올려준다
    # 값을 찾지 못한경우 적절한 위치를 찾기 위해 부등호 <= 를 사용해준다
    if array[mid] <= x:
      result = mid
      start = mid + 1
    elif array[mid] > x:
      end = mid - 1
    else:
      start = mid + 1

  # 값 삽입시 현재 인덱스보다 오른쪽에 삽입해야 하기때문에 1을 더해준다
  return result + 1

 

값을 찾지 못한경우 두 함수의 반환값은 모두 같다. 실제 bisect_left, bisect_right와 동일한 반환값을 가진다

 

따로 구현하지 않고 사용시에는 최상단에 from bisect import bisect_left, bisect_right 를 선언해주고 동일하게 쓰면 된다

 

 

- 부품 찾기

보유중인 부품들을 정렬한 후 이진 탐색을 실시하면 된다

더보기
# 퀵 정렬
def quick_sort(array, start, end):
  if start >= end:
    return

  pivot = start
  left = start + 1
  right = end

  while left <= right:
    while left <= end and array[left] <= array[pivot]:
      left += 1
    while right > start and array[right] >= array[pivot]:
      right -= 1
    if (left > right):
      array[right], array[pivot] = array[pivot], array[right]
    else:
      array[right], array[left] = array[left], array[right]

  quick_sort(array, start, right - 1)
  quick_sort(array, right + 1, end)

  return

# 이진 탐색
def binary_search(array, target, start, end):
  if start > end:
    return None
    
  mid = (start + end) // 2

  if array[mid] == target:
    return mid
  elif array[mid] > target:
    return binary_search(array, target, start, mid-1)
  else:
    return binary_search(array, target, mid+1, end)
    
  
  
n = int(input())
nlist = list(map(int, input().split()))

m = int(input())
mlist = list(map(int, input().split()))

quick_sort(nlist, 0, n - 1)

for x in mlist:
  result = binary_search(nlist, x, 0, n - 1)
  if result == None:
    print("no", end=' ')
  else:
    print("yes", end=' ')

 

퀵정렬은 구현하지 않고 리스트의 sort를 이용해도 되는데 연습할 겸 직접 작성해서 넣어봤음

 

[다이나믹 프로그래밍]

 

- 개미 전사

점화식을 만들어내는게 중요하다

더보기
n = int(input())
warehouse = list(map(int, input().split()))
dp = [0] * 100

dp[0] = warehouse[0]
dp[1] = max(warehouse[0], warehouse[1])

for i in range(2, n):
  dp[i] = max(dp[i-1], dp[i-2] + warehouse[i])

print(dp[n - 1])

 

점화식은 다음과 같이 정의할 수 있다.

한 칸 이상 떨어진 식량창고는 항상 털 수 있으므로 (i-3)번째 이하는 고려할 필요가 없다.

점화식을 유도해내는게 어렵다.

 

- 1로 만들기

그리디와 구분을 지을줄 알아야 하고 마찬가지로 점화식을 잘 세워야한다

더보기
x = int(input())
dp = [0] * 30001

# Bottom-up
# dp[1]은 0이라서 2부터 수행이 가능하다
for i in range(2, x + 1):
  # 현재 수에서 1을 빼는경우. 연산횟수 1회가 증가된다
  dp[i] = dp[i - 1] + 1

  if i % 2 == 0:
    # 현재 수가 2로 나누어 떨어지는 경우, 1을 뺀 현재 연산횟수보다
    # 2로 나누었을때의 연산횟수가 더 적은지 확인한다.
    # 2로 나누는 과정 자체도 한번의 연산횟수이기 때문에 1을 더해준 값을 비교한다.
    # 좌항은 연산횟수 1을 안더하는 이유는 이미 위에서 더해줬기 때문이다.
    dp[i] = min(dp[i], dp[i // 2] + 1)
    # 3으로 나누어 떨어지는 경우
  if i % 3 == 0:
    dp[i] = min(dp[i], dp[i // 3] + 1)
    # 5로 나누어 떨어지는 경우
  if i % 5 == 0:
    dp[i] = min(dp[i], dp[i // 5] + 1)

# dp에 저장된 값만 꺼내오면 된다
print(dp[x])

 

그리디처럼 큰 수인 5로 먼저 나누는것이 항상 능사가 아닌것을 알아야한다.

예를들어 27인경우 5로 먼저 나누려고 하면 26-25-5-1 총 4번의 연산이 이루어지지만 3으로 나눈다면 9-3-1 총 3번의 연산만으로 1을 만들수 있다. 그렇기에 그리디로는 풀 수 없다.

 

- 효율적인 화폐 구성

더보기
n, m = map(int, input().split())
dp = [10001] * (m + 1)

money = []
for i in range(n):
  money.append(int(input()))

dp[0] = 0
for i in money:
  for j in range(i, m + 1):
    # (i-k)원을 만드는 방법이 존재하는 경우
    if dp[j - i] != 10001:
      # 1을 더해주는 이유는 화폐 장수 한장이 추가되기 때문
      dp[j] = min(dp[j], dp[j - i] + 1)
    
if dp[m] == 10001:
  print(-1)
else:
  print(dp[m])

문제를 잘못 이해해서 30분정도 삽질을 했었다. 심플하게 잘 풀었다고 생각했는데 조건 자체가 잘못됐었음

 

 

[최단 경로]

 

- 미래 도시

어떤 최단 경로 알고리즘을 쓰는지 우선 파악해야한다.

더보기
from sys import stdin
input = stdin.readline
# 무한 값
INF = int(1e9)

n, m = map(int, input().split())

# 최단 거리 테이블 값을 무한으로 초기화
table = [[INF] * (n + 1) for _ in range(n + 1)]

# 자기 자신에서 자기 자신으로 가는 거리는 0
for i in range(1, n+1):
  table[i][i] = 0

for i in range(m):
  x1, x2 = map(int, input().split())
  # 양방향이면서 거리가 모두 1
  table[x1][x2] = 1
  table[x2][x1] = 1

# 목적지 x와 경유지k
x, k = map(int, input().split())

# 정점 개수만큼 순회 O(N)
for i in range(1, n + 1):
  # 2차원 최단 거리 테이블 갱신 O(N²)
  for x1 in range(1, n + 1):
    for x2 in range(1, n + 1):
      table[x1][x2] = min(table[x1][x2], table[x1][i] + table[i][x2])

# 1부터 경유지까지의 거리 + 경유지부터 목적지까지의 거리
distance = table[1][k] + table[k][x]

# INF가 넘는다면 경로가 없음
if distance >= INF:
  print(-1)
else:
  print (distance)

 

처음 다익스트라로 구현하다가 아닌 것 같아서 보니까 플루이드-마샬이었다.

그리고 공부 직후 구현하려니까 자잘한 부분이 생각이 안나서 조금씩 참고하면서 작성했다.

 

- 전보

도시-도시간의 최단 거리 문제로 치환할 수 있다

더보기
from sys import stdin
import heapq
input = stdin.readline
# 무한 값
INF = int(1e9)

n, m, c = map(int, input().split())

graph = [[] for _ in range(n + 1)]
distance = [INF] * (n + 1)

for _ in range(m):
  x, y, z = map(int, input().split())
  graph[x].append((y, z))

def dijkstra(start):
  q = []
  heapq.heappush(q, (0, start))
  distance[start] = 0

  while q:
    dist, now = heapq.heappop(q)

    if dist > distance[now]:
      continue

    for x in graph[now]:
      cost = dist + x[1]
      if cost < distance[x[0]]:
        distance[x[0]] = cost
        heapq.heappush(q, (cost, x[0]))

dijkstra(c)

count = 0
max_distance = 0

# 도달할 수 있는 노드중 가장 멀리있는 노드와의 최단거리
for x in distance:
  if x != INF:
    count += 1
    max_distance = max(max_distance, x)

# 시작 노드는 제외해야 하므로 -1
print(count - 1, max_distance)

 

다익스트라 알고리즘은 이제 생각하면서 짜면 95%정도는 나오는 수준은 됐음

마지막 도시 개수와 거리 뽑아내는 부분에서 살짝 뇌정지와서 책의 도움을 받았음

 

 

[그래프 이론]

 

- 팀 결성

서로소 집합 알고리즘을 사용하면 된다

더보기
from sys import stdin
input = stdin.readline

# 부모 리스트, 찾을값
def find_parent(parent, x):
  if parent[x] != x:
    parent[x] = find_parent(parent, parent[x])   
  return parent[x]

def union_parent(parent, a, b):
  a = find_parent(parent, a)
  b = find_parent(parent, b)

  if a < b:
    parent[b] = a
  else:
    parent[a] = b

n, m = map(int, input().split())
parent = [x for x in range(n + 1)] # 부모 리스트 초기화

for i in range(m):
  x, a, b = map(int, input().split())

  # union
  if x == 0:
    union_parent(parent, a, b)
  # find
  else:
    if find_parent(parent, a) == find_parent(parent, b): # 서로의 루트가 같다면
      print("YES")
    else:
      print("NO")

서로소 집합 알고리즘 생각나는대로 풀어서 쭉 쓰면 그대로 답임

 

- 도시 분할 계획

최소 신장 트리를 두개로 나누는 방법을 이용하면 된다

더보기
from sys import stdin
input = stdin.readline

def find_parent(parent, x):
  if parent[x] != x:
    parent[x] = find_parent(parent, parent[x])
  return parent[x]

def union_parent(parent, a, b):
  a = find_parent(parent, a)
  b = find_parent(parent, b)

  if a < b:
    parent[b] = a
  else:
    parent[a] = b

# 노드n 간선m. a,b,c : a-b의 연결비용 c
n, m = map(int, input().split())
parent = [x for x in range(n + 1)]

edges = [] # 간선 저장 리스트

for i in range(m):
  a, b, c = map(int, input().split())
  edges.append((c, a, b))

edges.sort()

answer = 0
max_cost = 0
for edge in edges:
  cost, a, b = edge

  # 사이클이 아닌 경우만 수행
  if find_parent(parent, a) != find_parent(parent, b):
    union_parent(parent, a, b)
    answer += cost
    max_cost = cost # 비용순으로 정렬되어서 가장 마지막에 들어온 값이 클수밖에 없음

# 가장 높은 비용을 빼서 트리를 2개로 분리
print(answer - max_cost)

 

- 커리큘럼

추후 다시 풀어볼 예정

 

'알고리즘 > 문제풀이' 카테고리의 다른 글

백준(BOJ) 문제 풀이  (0) 2022.07.20
코드업 문제 풀이  (0) 2022.07.15

+ Recent posts