Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

36 pknujsp #143

Merged
merged 7 commits into from
Mar 4, 2024
Merged

36 pknujsp #143

merged 7 commits into from
Mar 4, 2024

Conversation

pknujsp
Copy link
Collaborator

@pknujsp pknujsp commented Feb 21, 2024

이분 그래프, #135 PR

🔗 문제 링크

이분 그래프

✔️ 소요된 시간

1시간 이상
접근을 완전히 잘못하여 많이 소요

✨ 수도 코드

같은 집합 내의 노드끼리 서로 연결되지 않도록 그래프의 정점들을 두 집합으로 나눌 수 있는지 판별하는 문제

유니온 파인드로 풀이를 시작했다가 아닌 걸 깨닫기 까지 시간을 많이 쓴 문제입니다
일부 케이스만 보고 그래프 내에 사이클 여부가 중요하다고 생각했는데 아니었습니다

  • 이 문제는 정점들을 두 그룹으로 나누는 것이 목표이고
  • 이 과정에서 인접한 정점이 각각 다른 그룹에 속해야 하는 조건을 만족해야 합니다
  • 유니온 파인드를 사용하면 추가적인 조건을 고려해야 하면서
  • 인접한 정점이 다른 그룹에 속하는지 판단하는 로직이 복잡해집니다
  • 또한 사이클 판단이 핵심 로직이 아닙니다

1. 무방향 그래프를 생성

2. 정점을 그룹화하기 위해 리스트 생성

각 정점에 1 또는 -1의 값을 부여하여 그룹을 구분

3. 모든 노드 순회

  • 해당 노드의 id가 0인 경우 인접한 노드들에 대해서 BFS 수행
  • 탐색 과정에서 인접한 노드의 ID로 직전 노드의 ID에 음수를 취한 값으로 설정
  • 같은 그룹 내의 노드 간에는 인접하지 않아야 하므로, 서로 다른 값을 부여하는 것입니다

예시

  • 정점의 개수 : 4
  • 간선의 개수 : 4
  • 간선 : (1, 2), (2, 3), (3, 4), (4, 2)
단계 현재 정점 인접 정점 상태  
1 1 2 1: 1, 2: -1 1번 정점에서 시작하여 2번 정점을 -1로 설정
2 2 1, 3 1: 1, 2: -1, 3: 1 2번 정점의 인접 정점 중 3번 정점을 1로 설정
3 3 2, 4 1: 1, 2: -1, 3: 1, 4: 1 3번 정점의 인접 정점 중 4번 정점을 -1로 설정
4 4 2, 3 1: 1, 2: -1, 3: 1, 4: -1 4번 정점은 이미 id가 부여되어 있지 않음
def bfs(start):
    q = deque([start])
    groups[start] = 1

    while q:
        curr = q.popleft()
        visited[curr] = True

        for adj in graph[curr]:
            if visited[adj]:
                continue

            if not groups[adj]:
                groups[adj] = -groups[curr]
                q.append(adj)
            elif groups[adj] == groups[curr]:
                return False
    return True


for _ in range(int(stdin.readline())):
    V, E = map(int, stdin.readline().split())
    graph = [[] for i in range(V + 1)]

    for _ in range(E):
        a, b = map(int, stdin.readline().split())
        graph[a].append(b)
        graph[b].append(a)

    groups = [0] * (V + 1)
    visited = [False] * (V + 1)
    result = None

    for i in range(1, V + 1):
        if groups[i] == 0 and not bfs(i):
            result = 'NO'
            break

    print('YES' if not result else result)

📚 새롭게 알게된 내용

철로, #132 PR

🔗 문제 링크

철로

✔️ 소요된 시간

1시간 20분
50분 + 잠시 접고 + 30분

✨ 수도 코드

특정 길이의 철로를 하나만 깔 때, 이 철로를 이용하는 사람의 수가 최대가 되는 경우를 찾는 문제

입력

  • 철도 길이 : D
  • 사람 수 : n
  • 집, 회사의 좌표 목록 : [(h, o)...]

1. 입력 리스트 원소 정렬

  • 입력 리스트의 원소가 정렬되어 있지 않습니다
  • 집이 회사 보다 왼쪽에 있거나 오른쪽에 있을 수 있는데, 문제를 푸는 과정에서는 집과 회사의 구분이 불필요하기 때문에 쌍의 값들을 정렬해줍니다

2. 사용하는 변수

  • max_users : 최대 사용자 수
  • start_points_list : 최소 힙, 깔 수 있는 철로의 사용자 수를 저장

3. 집, 회사 좌표 리스트 순회하면서 철로 깔아보고 사용자 수 계산

  • 우선 좌표 리스트를 오른쪽 지점을 기준으로 오름차순 정렬합니다

현재 원소의 왼쪽 좌표 : start
오른쪽 좌표 : destination

start를 시작으로 철로를 깔고, 그 범위 내에 좌표 쌍이 모두 포함되는지 확인만 하면 되므로, 집과 회사를 구분할 필요가 없습니다

  • start에서 D를 더한 길이가 집 - 회사 사이의 거리보다 짧다면, 현재 쌍은 무시하고 다음 쌍으로 바로 넘어갑니다
    • 집 - 회사 간의 거리보다 짧다는 것은 이 사람은 철로를 못쓰는 것이므로 넘어가는 것
  • 집 - 회사 간의 거리보다 길거나 같다면 아래의 동작을 수행
  • start_points_liststart를 추가
  • start_points_list를 순회하면서 현재 깐 철도 범위내에 없는 값을 지웁니다
    • 다른 시작점에 철로를 깔았을 때 destination보다 작다면, 역시나 그 사람은 이 철로를 못 쓰는것이므로 제거
    • 순회하다가 철로를 쓸 수 있는 시작점을 만나면 순회를 종료합니다
  • max_users의 값을 갱신 : 현재 철로 사용자의 수인 start_points_list의 길이가 더 길다면 바꿉니다
points = []

for _ in range(int(stdin.readline().strip())):
    h, o = map(int, stdin.readline().strip().split())
    points.append([min(h, o), max(h, o)])

points.sort(key=lambda x: x[1])
D = int(stdin.readline().strip())

max_users = 0
start_points_list = []

for start, destination in points:
    if start + D < destination:
        continue
    heappush(start_points_list, start)
    
    while start_points_list:
        if start_points_list[0] + D >= destination:
            break
        heappop(start_points_list)
        
    max_users = max(max_users, len(start_points_list))

print(max_users)

📚 새롭게 알게된 내용

🔗 문제 링크

휴게소 세우기

✔️ 소요된 시간

30분

✨ 수도 코드

https://www.acmicpc.net/problem/2110 과 같은 유형의 문제

M개의 휴게소를 설치할 때 휴게소 간 거리의 최댓값 중 최솟값을 구하는 문제

  • 설치된 휴게소의 위치를 오름차순 정렬한다
  • 이진 탐색으로 휴게소 간 거리를 지정해서 추가한 휴게소의 개수로 탐색을 진행
    • left, right : 이진 탐색 범위, 휴게소 간의 가능한 최소/최대 거리
    • length : 이진 탐색 중에 고려하는 휴게소 간의 거리
    • installed : 휴게소 추가 개수
  • 휴게소 설치 조건 검사
    • 각 휴게소의 위치를 순회하면서, length 보다 큰 간격에 대해 추가로 휴게소를 설치할지 확인
    • diff : 이전 휴게소와 현재 휴게소 간의 거리
    • length < diff : 해당 구간에 휴게소를 설치한다
  • 마지막 휴게소와 고속도로 끝 사이의 처리
    • 해당 구간에 length 보다 큰 간격이 있다면, 그 구간에 추가로 설치한다
  • 탐색 조건 조정
    • installedM 보다 크다면, left 를 조정하여 범위를 넓힌다
    • 그렇지 않다면, right 를 조정하여 범위를 좁힌다
  • left 출력
    • 휴게소가 없는 구간의 최댓값중 최솟값
    • 최적의 휴게소 간 거리
    • 이 거리를 기준으로 M 개를 추가로 설치할 때 얻을 수 있는 최소한의 최대 간격이다

탐색 조건 조정

  • 예시
  • N : 1000
  • M : 2
  • array : [200, 500, 800]

left = 1, right = 999 : 휴게소 간의 거리가 최소 1, 최대 999

첫 번째 이진 탐색 수행 : 간격은 500, 휴게소 간의 최대 간격을 500으로 고려하는 것이다

  • 추가한 휴게소의 수가 M 보다 많다면
    • 간격 500 은 너무 커서 더 많이 설치해야 하는 것을 의미
    • 간격을 더 좁힌다
  • 적다면
    • 간격 500이 조건을 만족하거나, 더 좋은 조건을 찾을 수도 있으므로
    • 간격을 더 넓힌다
N, M, L = map(int, input().split())
array = list(map(int, input().split()))
array.sort()

left = 1
right = L - 1

min_max_distance = right

while left <= right:
    max_distance = (left + right) // 2
    extra_added = 0
    last_installed_x = 0
    
    for installed_x in array:
        if installed_x - last_installed_x > max_distance:
            extra_added += (installed_x - last_installed_x - 1) // max_distance
            
        last_installed_x = installed_x
        
    if L - last_installed_x > max_distance:
        extra_added += (L - last_installed_x - 1) // max_distance

    if extra_added > M:
        left = max_distance + 1
    else:
        right = max_distance - 1

print(left)

📚 새롭게 알게된 내용

Copy link
Member

@tgyuuAn tgyuuAn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와 이 문제 대박

첨에 거리 사이사이 마다 어떻게 수를 정하지? 생각해서 당연히 완탐인 줄 알았는데,,

from heapq import *
import sys

N, M, L = map(int,input().split())

rest_areas = [0]
rest_areas.extend(list(map(int,input().split())))
rest_areas.sort()
rest_areas.append(L)
heap = []

for idx in range(1,len(rest_areas)):
    heappush(heap, -1 * (rest_areas[idx] - rest_areas[idx-1]))

answer = int(1e9)

def dfs(heap, remain_count):
    global answer
    
    if remain_count == 0:
        answer = min(answer, -1*heappop(heap))
        return
           
    for count in range(remain_count,0,-1):
        now_heap = heap[:]
        now = heappop(now_heap)
        
        for _ in range(count+1):
            heappush(now_heap,now//(count+1))

        dfs(now_heap, remain_count-count)
dfs(heap, M)
print(answer)

바로 시간 초과네요..

중간에 원소를 삽입하는 과정에서 얇은 카피를 써야하는데 여기서 시간이 터지는 것 같네요.

이분 탐색써서 범위를 설정하는 기술 많이 풀어왔는데 왜 생각이 안났찌 ㅎㄷ;;

다시 풀어보고 오겠씁ㄴ비다..

Copy link
Member

@tgyuuAn tgyuuAn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음엔 그리디 인 줄 알았다가, 말도 안되는 것 같아서 완탐으로 시도 해봤습니다.

from heapq import *
import sys

N, M, L = map(int,input().split())

rest_areas = [0]
rest_areas.extend(list(map(int,input().split())))
rest_areas.sort()
rest_areas.append(L)
heap = []

for idx in range(1,len(rest_areas)):
    heappush(heap, -1 * (rest_areas[idx] - rest_areas[idx-1]))

answer = int(1e9)

def dfs(heap, remain_count):
    global answer
    
    if remain_count == 0:
        answer = min(answer, -1*heappop(heap))
        return
           
    for count in range(remain_count,0,-1):
        now_heap = heap[:]
        now = heappop(now_heap)
        
        for _ in range(count+1):
            heappush(now_heap,now//(count+1))

        dfs(now_heap, remain_count-count)
dfs(heap, M)
print(answer)

근데 시간 초과가 나더라고요.

택도 없었쬬..







와 근데 이거 이분 탐색으로 범위 설정하면서 구하는 트릭이었네요..

많이 풀었었는데 왜 생각을 못했지..

PR보고 다시 풀었습니다.




근데 아래와 같이 제출하니 안되더라고요. 왜지..?

import sys

N, M, L = map(int,input().split())

rest_areas = [0]
if N != 0 : rest_areas.extend(list(map(int,input().split())))
rest_areas.sort()
rest_areas.append(L)
gap = []

for idx in range(1,len(rest_areas)):
    gap.append(rest_areas[idx] - rest_areas[idx-1])

def check(mid, gap, remain_count):
    for now in gap:
        if now <= mid: continue
    
        count = 1
        while True:
            
            if mid >= now // (count+1):
                remain_count -= count
                break
            
            count += 1
            
        if remain_count < 0: return False
    return True

left = 0
right = L
maximum_gap = 0

while left+1 < right:
    mid = (left + right)//2
    
    if check(mid, gap, M):
        maximum_gap = mid
        right = mid
        
    else:
        left = mid
        
new_rest_areas = []
for now in gap:
    if now <= maximum_gap: 
        new_rest_areas.append(now)   
        continue
    
    count = 1
    while True:
            
        if maximum_gap >= now // (count+1): 
            new_rest_areas.append(-1*(-1*now // (count+1)))  
            break
            
        count += 1

new_rest_areas.sort()        
print(new_rest_areas[-1])

저는 정확히 n 등분으로 나눠서 통과할 수 있으면 check에서 true를 반환하도록 설정했는데,

준성님 코드 처럼 그냥 max_length만큼 나눠서 통과하도록 바꾸니까 또 통과가 되네요.

왜지 ?

import sys

N, M, L = map(int,input().split())

rest_areas = [0]
if N != 0: rest_areas.extend(list(map(int,input().split())))
rest_areas.sort()
rest_areas.append(L)
gap = []

for idx in range(1,len(rest_areas)):
    gap.append(rest_areas[idx] - rest_areas[idx-1])

def check(mid, gap, remain_count):
    for now in gap:
        if now <= mid: continue
    
        remain_count -= ((now-1)//mid)
    
        if remain_count < 0: return False
    return True

left = 0
right = L+1
maximum_gap = 0

while left+1 < right:
    mid = (left + right)//2
    
    if check(mid, gap, M):
        maximum_gap = mid
        right = mid
        
    else:
        left = mid
        
new_rest_areas = []
for now in gap:
    if now <= maximum_gap: 
        new_rest_areas.append(now)   
        continue
    
    while now > maximum_gap:
        now -= maximum_gap
    
    new_rest_areas.append(maximum_gap)
    new_rest_areas.append(now)
    
new_rest_areas.sort()        
print(new_rest_areas[-1])

흠...

@pknujsp
Copy link
Collaborator Author

pknujsp commented Mar 4, 2024

2 147 150
67 70
반례 막하다가 찾긴했는데 결과가 다른 정확한 원인을 아직 모르겠네요
더 탐구해보겠습니다

@pknujsp pknujsp merged commit 5749510 into main Mar 4, 2024
@pknujsp pknujsp deleted the 36-pknujsp branch March 4, 2024 07:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants