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

18-H0ngJu #199

Merged
merged 1 commit into from
May 30, 2024
Merged

18-H0ngJu #199

merged 1 commit into from
May 30, 2024

Conversation

H0ngJu
Copy link
Collaborator

@H0ngJu H0ngJu commented May 26, 2024

🔗 문제 링크

계란으로 계란치기

✔️ 소요된 시간

✨ 수도 코드

image

사실 저번 PR 때 쓰려다가 못풀어서 이번 PR로 들고 왔습니다

문제요약
다음 과정을 거쳐 깰 수 있는 최대 계란 수를 반환하라 @!

  1. 가장 왼쪽의 계란을 든다.
  2. 손에 들고 있는 계란이 깨지지 않았다면, 다른 계란 중 하나를 친다. (단, 손에 들고 있는 계란이 깨졌거나 칠 수 있는 계란이 없다면 넘어간다.)
  3. 가장 최근에 든 계란의 한 칸 오른쪽 계란을 손에 들고 2번 과정을 다시 진행한다. 단, 가장 최근에 든 계란이 가장 오른쪽에 위치한 계란일 경우 계란을 치는 과정을 종료한다.


수도 코드는 간단합니다

  1. 지금 손에 든 계란이 마지막 계란이면 깨진 계란 수를 반환
  2. 손에 든 계란이 이미 깨진 계란이면 다음 계란으로 넘어가기
  3. 1&2가 아닌 경우, 계란을 깨기
  4. 계란을 깰 수 없는 경우(깰 계란이 없음), 다음 계란으로 넘어가기


문제를 풀 때 주의할 점은,

계란을 깨는 과정이 백트래킹이기 때문에 다시 깼던 계란을 다시 돌려주는 것입니다.

for j in range(N): # 계란 깨기
        if j != idx and eggs[j][0] > 0:
            broken = True
            eggs[idx][0] -= eggs[j][1]
            eggs[j][0] -= eggs[idx][1]

            cnt = max(cnt, break_egg(idx+1))

            eggs[idx][0] += eggs[j][1]
            eggs[j][0] += eggs[idx][1]

eggs[idx]계란으로 자기 자신을 제외한 eggs[0]~eggs[N-1]을 깹니다. 다음 계란으로 넘어가기 전에 재귀를 해주고, 깨지기 이전 상태로 돌려주어야 합니다.

저는 이 부분에서 다른 레퍼런스를 참고했습니다

📚 새롭게 알게된 내용

Copy link
Collaborator

@SeongHoonC SeongHoonC left a comment

Choose a reason for hiding this comment

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

저는 브루트포스로 매번 새로운 리스트를 만들어서 넘겨줬네요... 변수가 많아서 좀 오래걸렸습니다..ㅎ

import java.io.BufferedReader
import java.io.InputStreamReader
import kotlin.math.max

var egg_count_max = 0

fun main() {
    val br = BufferedReader(InputStreamReader(System.`in`))
    val n = br.readLine().toInt()

    val heart = Array(n) { 0 }
    val weight = Array(n) { 0 }

    repeat(n) {
        val (h, w) = br.readLine().split(" ").map { it.toInt() }
        heart[it] = h
        weight[it] = w
    }

    hit(n, 0, heart.toList(), weight.toList())
    println(egg_count_max)
}

private fun hit(n: Int, now: Int, heart: List<Int>, weight: List<Int>) {
    // 끝에 도달
    if (now == n) {
        egg_count_max = max(egg_count_max, heart.count { it <= 0 })
        return
    }

    // 끝에 도달하지 않아도 더 깰 계란이 없음
    if (n - 1 == heart.count { it <= 0 }) {
        egg_count_max = max(egg_count_max, n - 1)
        return
    }

    // 이미 now 가 깨졌으면 안깨고 넘기기
    if (heart[now] <= 0) {
        hit(n, now + 1, heart, weight)
        return
    }
    
    // 꺠고 넘기기
    for (i in 0 until n) {
        // now 자신
        if (i == now) continue

        // 이미 깨진 계란
        if (heart[i] <= 0) continue

        // 계란 부딪히기
        val _heart = heart.toMutableList()
        _heart[now] -= weight[i]
        _heart[i] -= weight[now]
        hit(n, now + 1, _heart, weight)
    }
}

Comment on lines +31 to +32
eggs[idx][0] += eggs[j][1]
eggs[j][0] += eggs[idx][1]
Copy link
Collaborator

Choose a reason for hiding this comment

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

저는 여기서 돌려주지않고 그냥 매번 새로운 리스트를 생성합니다!

Copy link
Member

Choose a reason for hiding this comment

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

@SeongHoonC

매번 새로운 리스트를 생성하면 $O(N)$의 시간복잡도로 생성되는데,

위와 같이 백트래킹 방식으로 갔다가 다시 되돌려놓으면 $O(1)$로 풀 수 있어서 훨씬 빨라요!

Copy link

@9kyo-hwang 9kyo-hwang left a comment

Choose a reason for hiding this comment

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

이거 처음 봤을 때 문제 이해가 잘 안돼서 살짝 당황했던 기억이... ㅋㅋ


input = open(0).readline

N = int(input())
S, W = [0] * N, [0] * N
for i in range(N):
    s, w = map(int, input().split())
    S[i], W[i] = s, w
    

def backtracking(current: int = 0, broken: int = 0) -> int:
    if broken == N - 1 or current == N:  # 모든 계란이 깨졌거나 끝까지 왔다면면
        return broken
        
    if S[current] <= 0:  # 이미 깨짐
        return backtracking(current + 1, broken)
        
    count: int = 0
    for nxt in range(N):
        if current == nxt or S[nxt] <= 0:  # 같은 계란이거나 이미 깨졌다면 skip
            continue
        
        S[current] -= W[nxt]
        S[nxt] -= W[current]
        
        count = max(count, backtracking(current + 1, broken + (S[current] <= 0) + (S[nxt] <= 0)))
        
        S[current] += W[nxt]
        S[nxt] += W[current]
    
    return count

    
print(backtracking())

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

def break_egg(idx):

Choose a reason for hiding this comment

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

백트래킹 함수의 인자로 "계란을 몇 개 깼는가" 정보를 같이 넘겨주게 되면 조금 더 효율적으로 동작하게 만들 수 있습니다

def backtracking(current: int = 0, broken: int = 0) -> int:
    if broken == N - 1 or current == N:  # 모든 계란이 깨졌거나 끝까지 왔다면
        return broken
        
    if S[current] <= 0:  # 이미 깨짐
        return backtracking(current + 1, broken)

    count: int = 0
    for nxt in range(N):
        if current == nxt or S[nxt] <= 0:  # 같은 계란이거나 이미 깨졌다면 skip
            continue
        
        S[current] -= W[nxt]
        S[nxt] -= W[current]
        
        count = max(count, backtracking(current + 1, broken + (S[current] <= 0) + (S[nxt] <= 0)))
        
        S[current] += W[nxt]
        S[nxt] += W[current]
    
    return count

broken 인자를 들고 있기 때문에 리스트를 순회해서 깨진 계란 개수를 카운팅하는 과정 자체가 사라집니다.

Choose a reason for hiding this comment

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

제 코드의 경우엔 재귀 제한을 안풀어줘도 되더라구요 :)

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.

N이 최대 8이라는 점에서

8^8의 시간 복잡도가 최대

즉, $(2^3)^8 = 2^24$ 니까

$2^{10} * 2^{10} * 2^4 = 1024 * 1024 * 16 -&gt; 약 1600 만$ 시간복잡도




백트래킹으로 쌉가능이라고 판단하구 풀었씁니다 히히히

변수 되돌리는 부분에서 좀 귀찮았는데 원큐에 빵야! 하고 풀어서 재밌었씁니둥

import sys

def input(): return sys.stdin.readline().rstrip()

N = int(input())
eggs = []
is_broken = [False for _ in range(N)]
for _ in range(N):
    durability, weight = map(int, input().split())
    eggs.append([durability, weight])

answer = 0
def dfs(eggs, is_borken, depth):
    global answer
    
    if depth == len(eggs):
        answer = max(answer, sum(is_borken))
        return
    
    if is_borken[depth]:
        dfs(eggs, is_borken, depth+1)
        return
    
    can_hit = False

    now_egg = eggs[depth]
    now_durability, now_weight = now_egg[0], now_egg[1]

    for poor_idx in range(N):
        if poor_idx == depth: continue
        if is_borken[poor_idx]: continue
    
        can_hit = True
        
        poor_egg = eggs[poor_idx]
        poor_durability, poor_weight = poor_egg[0], poor_egg[1]

        new_now_durability = now_durability - poor_weight
        new_poor_durability = poor_durability - now_weight
        
        if new_now_durability <= 0: is_borken[depth] = True
        if new_poor_durability <= 0: is_borken[poor_idx] = True

        eggs[depth], eggs[poor_idx] = [new_now_durability, now_weight], [new_poor_durability, poor_weight]
        dfs(eggs, is_borken, depth+1)
        eggs[depth], eggs[poor_idx] = [now_durability, now_weight], [poor_durability, poor_weight]

        is_borken[depth] = False
        is_borken[poor_idx] = False
        
    if can_hit == False: answer = max(answer, sum(is_borken))
    return

dfs(eggs, is_broken, 0)
print(answer)

image

@alstjr7437
Copy link
Member

헉 멍청하게 가장 왼쪽에 먼저 들고 아무거나 치는 것인데 생각을 잘못해서
아무거나 들고 아무거나 치는 줄 알고

3
1 100
8 5
3 5

가 왜 2인가.. 생각하고 있었는데 문해력 이슈가 일어났군요..

이해하고 아래와 같이 풀었습니다!

def egg_crack(index, eggs):
    if index == len(eggs):
        return sum(1 for durability, _ in eggs if durability <= 0)
    
    if eggs[index][0] <= 0:
        # 현재 계란 깨졋으면 다음 계란
        return egg_crack(index + 1, eggs)
    
    max_broken = 0
    hit = False  # 다른 계란 쳤는지
    
    for i in range(len(eggs)):
        if i != index and eggs[i][0] > 0:
            hit = True
            eggs[index][0] -= eggs[i][1]
            eggs[i][0] -= eggs[index][1]
            
            max_broken = max(max_broken, egg_crack(index + 1, eggs))
            
            eggs[index][0] += eggs[i][1]
            eggs[i][0] += eggs[index][1]
    
    # 칠 수 있는 계란이 없으면 다음 계란으로 넘어감
    if not hit:
        max_broken = max(max_broken, egg_crack(index + 1, eggs))
    
    return max_broken

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

print(egg_crack(0, egg))

Comment on lines +23 to +24
for j in range(N): # 계란 깨기
if j != idx and eggs[j][0] > 0:
Copy link
Member

Choose a reason for hiding this comment

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

따로 사소한 궁금점인데...
혹시 j로 반복문을 돌리신 이유가 있으실까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

음 왜 j냐고 물으신다면 .... i 다음에 j 쓰는게 습관이어요
큰 의미는 없슴다!

@H0ngJu H0ngJu merged commit dccde32 into main May 30, 2024
9 checks passed
@H0ngJu H0ngJu deleted the 18-H0ngJu branch May 30, 2024 14:25
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.

5 participants