문제

상근이는 문자열에 폭발 문자열을 심어 놓았다. 폭발 문자열이 폭발하면 그 문자는 문자열에서 사라지며, 남은 문자열은 합쳐지게 된다.

폭발은 다음과 같은 과정으로 진행된다.

  • 문자열이 폭발 문자열을 포함하고 있는 경우에, 모든 폭발 문자열이 폭발하게 된다. 남은 문자열을 순서대로 이어 붙여 새로운 문자열을 만든다.
  • 새로 생긴 문자열에 폭발 문자열이 포함되어 있을 수도 있다.
  • 폭발은 폭발 문자열이 문자열에 없을 때까지 계속된다.

상근이는 모든 폭발이 끝난 후에 어떤 문자열이 남는지 구해보려고 한다. 남아있는 문자가 없는 경우가 있다. 이때는 "FRULA"를 출력한다.

폭발 문자열은 같은 문자를 두 개 이상 포함하지 않는다.

입력

첫째 줄에 문자열이 주어진다. 문자열의 길이는 1보다 크거나 같고, 1,000,000보다 작거나 같다.

둘째 줄에 폭발 문자열이 주어진다. 길이는 1보다 크거나 같고, 36보다 작거나 같다.

두 문자열은 모두 알파벳 소문자와 대문자, 숫자 0, 1, ..., 9로만 이루어져 있다.

출력

첫째 줄에 모든 폭발이 끝난 후 남은 문자열을 출력한다.

내가 푼 풀이

접근방법: 1. Swift에 내장된 문자열 메서드(시간초과)

 

스택을 사용하면 될꺼라 생각했지만, 더 간단하게 Swift에서 제공하는 메서드를 이용하면 되지 않을까 했다.

import Foundation
// 입력받기
var str = readLine()!
let bomb = readLine()!

// 폭발 문자열 포함이 되었다면 해당 문자열을 빈 문자열로 치환
while str.contains(bomb) {
    str = str.replacingOccurrences(of: bomb, with: "")
}
// 모두 바꾸고, 문자열이 비어있다면 FRULA, 아니라면 문자열 출력
if str.isEmpty {
    print("FRULA")
} else {
    print(str)
}

 replacingOccurrences 메서드는 해당 문자열을 다른 문자열로 치환해준다.

코드도 간결해서 바로 통과될 줄 알았지만, 해당 문자열에 포함되었는지 확인하는 contains는 O(N), 

replacingOccurrences는 보기에 문자를 바로 치환해주지만, 주어진 문자열을 모두 검사하고, 치환하는 과정을 거친다 O(N)

따라서 위 코드의 시간복잡도는 아마 O($N^{2}$)과 근접한 시간이 걸린다.. 그래서 44퍼에서 시간초과가 났다.

 

 

접근방법 2: 스택을 사용

주어진 문자열을 순회하며, 스택에 push하고,

스택에 상단문자부터 폭발문자열의 길이만큼 뽑아낸 문자열이 폭발문자열인경우 모두 pop해준다.

스택이 비어있다면 FRULA, 비어있지않다면 문자열로 출력

 

import Foundation

// 입력받기
var leftStack = Array(readLine()!)
let bomb = Array(readLine()!)
let bCount = bomb.count
var rightStack = [Character]()

// 스택에 넣기
while !leftStack.isEmpty {
    rightStack.append(leftStack.removeLast())
    // 스택이 폭발 문자열길이만큼 넣어졌다면 검사
    if rightStack.count >= bCount {
        check()
    }
}
// 스택의 상단원소부터 폭발 문자열 길이만큼의 문자를 뽑아서, 해당 문자와 폭발 문자열이 같은 경우 pop
func check() {
    var s = [Character]()
    for i in (rightStack.count-bCount..<rightStack.count).reversed() {
        s.append(rightStack[i])
    }
    if s == bomb {
        for i in 0..<s.count {
            rightStack.removeLast()
        }
    }
}

if rightStack.isEmpty {
    print("FRULA")
} else {
    print(String(rightStack.reversed()))
}

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-오아시스 재결합 Swift  (0) 2024.05.10
BOJ-17299 오등큰수 Swift  (0) 2024.05.10
BOJ-11444 피보나치 수 6 Swift  (0) 2024.05.05
BOJ-10830 행렬 제곱 Swift  (0) 2024.05.05
BOJ-11401 이항 계수 3 Swift  (0) 2024.05.03

문제

피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다.

이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n ≥ 2)가 된다.

n=17일때 까지 피보나치 수를 써보면 다음과 같다.

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597

n이 주어졌을 때, n번째 피보나치 수를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 n이 주어진다. n은 1,000,000,000,000,000,000보다 작거나 같은 자연수이다.

출력

첫째 줄에 n번째 피보나치 수를 1,000,000,007으로 나눈 나머지를 출력한다.

내가 푼 풀이

문제를 보았을때 이문제는 수학적 성질이 요구되는 문제일꺼다 생각했다

Swift 언어로 문제를 풀기에는 입력수를 담을 배열을 선언할 수도 없고, 입력값이 너무커서 분할정복을 이용한 재귀호출도 시간초과가 떴다.

다른사람의 풀이를 참고했다.

 

접근방법: 분할정복, 피보나치수열의 성질

보통 피보나치 수열의 도가뉴항등식을 이용하여 분할정복을 했지만, 입력값이 너무커서 시간초과가 났다

도가뉴 항등식

$F_{m+n} = F_{m-1}F_{n} + F_{m}F_{n+1} $

 

주어진 n이 짝수일때 m = n:

$F_{2n} = F_{n-1}F_{n} + F_{n}F_{n+1} $ = $F_{n}( F_{n-1} + F_{n+1})$

여기서 $F_{n+1} = F_{n} + F_{n-1}$ 이므로

$F_{n}( F_{n-1} + F_{n+1})$ = $F_{n}(2F_{n-1} + F_{n})$ 로 나타낼 수 있다.

 

주어진 n이 홀수일때 m = n+1:

$F_{2n+1} = F_{n}F_{n} + F_{n+1}F_{n+1} $  = $F_{2n+1} =(F_{n})^{2} + (F_{n+1})^{2}$

 

따라서 n이 홀수일때와 짝수일때 분할정복하여 결과를 출력했었는데... 시간초과가 났다

<시간초과 코드>

더보기
import Foundation

// 입력
var n = Int(readLine()!)!
let p = 1000000007

// 분할 정복
func divide(_ n: Int) -> Int {
    if n == 0 { return 0}
    if n == 1 { return 1}
    if n == 2 { return 1}
    let m = divide(n/2)
    if n % 2 == 0 {
        let m1 = divide(n/2-1)
        return m * (2 * m1 + m) % p
    } else {
        let m1 = divide(n/2 + 1)
        return ((m * m) + (m1 * m1)) % p
    }
}
print(divide(n))

아무래도 재귀호출이 너무남발되어 시간초과가 날꺼라 생각하고, 이를 dp형식으로 배열에 저장하고 값을 불러오자 하여 바꾸어보았다.

그래도 시간초과가 일어났다.

더보기
import Foundation

// 입력받기
var n = Int(readLine()!)!
let p = 1000000007
// 초기값 설정
var dp = Array(repeating: 0, count: n+3)
dp[0] = 0
dp[1] = 1
dp[2] = 1

// 분할 정복
func divide(_ n: Int) -> Int {
    if n == 0 || n == 1 || n == 2 { return dp[n] }
    let m = divide(n/2)
    if n % 2 == 0 {
        let m1 = divide(n/2-1)
        dp[n] = m * (2 * m1 + m) % p
    } else {
        let m1 = divide(n/2 + 1)
        dp[n] = ((m * m) + (m1 * m1)) % p
    }
    return dp[n]
}
print(divide(n))

위 두개의 코드는 재귀호출 수는 차이가 없어보이는데 아래 dp배열을 이용한것은 입력값만큼의 수를 담을 배열을 만들 수가 없었다.

그래서 런타임에러가 떴다..

다른 언어는 잘만되는데 ;

 

그러다가 피보나치수열을 행렬의곱셈으로 구하는 방법을 찾아보게되었다.

https://www.acmicpc.net/blog/view/28

 

행렬의 곱셈으로 피보나치수열을 구하는방법인데 이 방법은 위 방법과 다르게 입력값크기의 배열을 구현하지 않아도 되고, 재귀호출도 남발하지 않게된다.

import Foundation

// 입력
var n = Int(readLine()!)!
let p = 1000000007
// 피보나치 수열을 행렬곱셈으로 구하기
var arr: [[Int]] = [[1,1],[1,0]]

// 행렬 곱셈
func multi(_ x: [[Int]],_ y: [[Int]]) -> [[Int]]{
    var result = Array(repeating: Array(repeating: 0, count: 2), count: 2)
    for i in 0..<2 {
        for j in 0..<2{
            for k in 0..<2 {
                result[i][j] += x[i][k] * y[k][j]
                result[i][j] %= p
            }
        }
    }
    return result
}
//
// 분할 정복
func divide(_ n: Int) -> [[Int]] {
    // n이 1이면 배열 리턴
    if n == 1 { return arr }
    
    // n이 1이될때까지 분할
    let m = divide(n/2)
    if n % 2 == 0 {
        return multi(m,m)
    } else {
        return multi(multi(m,m), arr)
    }
}
// 출력
print(divide(n)[0][1])

 

참 수학의 세계는 어렵다..

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-17299 오등큰수 Swift  (0) 2024.05.10
BOJ-9935 문자열 폭발 Swift  (0) 2024.05.07
BOJ-10830 행렬 제곱 Swift  (0) 2024.05.05
BOJ-11401 이항 계수 3 Swift  (0) 2024.05.03
BOJ-1629 곱셈 Swift  (0) 2024.05.03

문제

크기가 N*N인 행렬 A가 주어진다. 이때, A의 B제곱을 구하는 프로그램을 작성하시오. 수가 매우 커질 수 있으니, A^B의 각 원소를 1,000으로 나눈 나머지를 출력한다.

입력

첫째 줄에 행렬의 크기 N과 B가 주어진다. (2 ≤ N ≤  5, 1 ≤ B ≤ 100,000,000,000)

둘째 줄부터 N개의 줄에 행렬의 각 원소가 주어진다. 행렬의 각 원소는 1,000보다 작거나 같은 자연수 또는 0이다.

출력

첫째 줄부터 N개의 줄에 걸쳐 행렬 A를 B제곱한 결과를 출력한다.

내가 푼 풀이

접근방법: 분할정복

첫번째로 행렬의 곱셈을 먼저 구현한다. (for 3중반복문)

거듭제곱을 그대로 곱하면 1000억번 연산해야하므로 시간초과가 무조건 난다.

여기서 거듭제곱을 지수법칙을 적용해 분할정복 형식으로 곱한다.

 

$ A^{B} = A^{\frac{B}{2}} \times A^{\frac{B}{2}} $ 이 성립하므로 지수가 주어지면 1이 될때까지 반으로 분할한다.

지수가 홀수:

 $A^{B} = A^{\frac{B}{2}} \times A^{\frac{B}{2}} \times A $

지수가 짝수:

 $A^{B} = A^{\frac{B}{2}} \times A^{\frac{B}{2}}$

 

1까지 분할이 됬다면 두 행렬을 곱한다(정복)

 

코드로 구현하면 다음과 같다.

import Foundation

// 입력받기
var input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], B = input[1]
var arr = [[Int]]()
for i in 0..<N {
    arr.append(readLine()!.split(separator: " ").map{Int(String($0))!})
}

// 분할 정복
func divide(_ N: [[Int]],_ B: Int) -> [[Int]]{
    // 지수가 1이 될때 까지 분할
    if B == 1 {
        return N
    }
    // 지수를 반으로 분할
    let m = divide(N, B/2)
    // 지수가 짝수인경우 나머지가 없으므로 나눈 두 행렬을 곱
    if B % 2 == 0 {
        return multi(m, m)
    } else {
        // 지수가 홀수라면 나머지1이 존재하므로 나눈 두 행렬과 지수가 1인 행렬 곱
        return multi(multi(m, m), N)
    }
}
//행렬 곱
func multi(_ x: [[Int]],_ y: [[Int]]) -> [[Int]] {
    var result = Array(repeating: Array(repeating: 0, count: N), count: N)
    for i in 0..<N {
        for j in 0..<N {
            for k in 0..<N {
                result[i][j] += ((x[i][k] * y[k][j]))
            }
            result[i][j] %= 1000
        }
    }
    return result
}

// 지수가 1이고, 행렬의 원소가 1000 이상이라면 출력은 해당원소를 1000으로 나눠야 하므로 한번 나눠준다.
var result = divide(arr, B)
for i in 0..<result.count {
    for j in 0..<result[i].count {
        result[i][j] %= 1000
    }
}

// 출력
for i in 0..<result.count {
    print(result[i].map{String($0)}.joined(separator: " "))
}

 

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-9935 문자열 폭발 Swift  (0) 2024.05.07
BOJ-11444 피보나치 수 6 Swift  (0) 2024.05.05
BOJ-11401 이항 계수 3 Swift  (0) 2024.05.03
BOJ-1629 곱셈 Swift  (0) 2024.05.03
BOJ-1780 종이의 개수 Swift  (0) 2024.05.03

문제

자연수 과 정수 가 주어졌을 때 이항 계수 를 1,000,000,007로 나눈 나머지를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 가 주어진다. (1 ≤  ≤ 4,000,000, 0 ≤   )

출력

 를 1,000,000,007로 나눈 나머지를 출력한다.

내가 푼 풀이

수학적 지식의 벽을 느낀 문제였다... 다른사람의 풀이를 보고 이해하는데도 오래걸렸다.

하지만 알아두면 좋을 것 같아서 이렇게 기록해둔다.

 

접근방법: 분할정복, 페르마의 소정리, 모듈러 연산, 이항계수, 지수법칙

1. 이항계수 구하기: \(\binom{N}{K}\) = ${_N}C{_K}$ =  $\frac{N!}{(N-K)! \times K!}$

 

페르마의 소정리

 

소수 $p$와 정수 $a$에 대해서  ${\displaystyle p\nmid a}$일때,

$a^{p} \equiv  a(\mathrm{mod} p)$ 이며 $a\neq 0 $ 라면

$a^{p-1} \equiv  1(\mathrm{mod} p)$ 이다.

 

${\displaystyle p\nmid a}$: a는 p의 약수가 아니다.

합동식 $\equiv$ 의 의미는 아래와 같다.

$a \equiv b (\mathrm{mod} p)$ : a를 p로 나눈 나머지와 b를 p로 나눈 나머지는 같다.

이 점을 생각해서 위 식은 $ a^{p-1}$를 $p$로 나눈 나머지와 $1$을 $p$로 나눈 나머지는 같다라고 의미한다.

 

문제에서 p는 1,000,000,007이고, 이는 소수이므로 페르마의 소정리를 이용할 수 있다.

 

${_N}C{_K}$ =  $\frac{N!}{(N-K)! \times K!}$ = $N! \times((N-K)! \times K!)^{-1}$ 로 표현 할 수 있고,

최종적으로 아래의 식을 구하면 된다.

 

정답: $(N! \times((N-K)! \times K!)^{-1}) \mathrm{mod}p $ $( p = 1,000,000,007)$

 

여기서 모듈러의 연산중 분배법칙을 적용하면 

$(N! \times((N-K)! \times K!)^{-1}) \mathrm{mod}p $ = $(N!\mathrm{mod}p \times((N-K)! \times K!)^{-1}\mathrm{mod}p)\mathrm{mod}p$

 

페르마의 소정리 를 적용하면

$a^{p-1} \equiv  1(\mathrm{mod} p)$ = $a \times a^{p-2} \equiv  1(\mathrm{mod} p)$

=$ a^{-1}(\mathrm{mod}p) \equiv a^{p-2}(\mathrm{mod}p)$ ( $\frac{1}{a} = a^{-1}$ 이므로)

로 나타낼 수 있다.

여기서 a 가 $ ((N-K)! \times K!)^{-1} $ 라면 

$ ((N-K)! \times K!)^{-1} $ = $ ((N-K)! \times K!)^{p-2} $ ( p = 1,000,000,007) 가 된다

 

많이왔다..

최종적으로 우리는 아래의 답을 구하면된다.

정답:  $(N!\mathrm{mod}p \times((N-K)! \times K!)^{1,000,000,007-2}\mathrm{mod}p)\mathrm{mod}p$

1,000,000,007 - 2 제곱은 단순히 실행만해도 1,000,000,005번 실행하게된다.

분할정복은 바로 제곱수를 구할때 적용한다.

지수법칙을 이용하면 다음과 같다. $x^{ab} = x^{a} \times x^{b}$

제곱수의 p-2가 1이 될때까지 분할하고, 1이되면 정복하여 해당 값을 리턴한다.

이때 모듈러의 연산에 의해 값을 리턴할 때 p로 나누어준다.

 

정말 어려웠다 이해하는것도 어려웠고 잘 이해했는지도 모르겠다.

하지만 이 같은 문제가 다시 나온다면 그래도 풀이시작을 해볼 수 있을것같다.

 

코드로 구현하면 다음과 같다.

import Foundation

// 입력
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], K = input[1]
let p = 1000000007
// 팩토리얼 계산
// 0! = 1이다. 4! = 4 * 3 * 2 * 1 = 4 * 3!으로 나타냄
// 이와같이 1!을 표현하면 1! = 1 * 0!이다. 따라서 0! = 1
func factorial(_ num: Int)-> Int {
    var n = 1
    if num <= 1 { return 1}
    
    for i in 2...num {
        n = n * i % p
    }
    return n
}
// 분할 정복
func divide(_ n: Int,_ k: Int) -> Int{
    // 지수가 1이 되면 값 리턴 (정복)
    if k == 1 { return n % p}
    // 분할
    let m = divide(n, k/2)
    
    // 지수가 1이될때까지 분할한다.
    // 지수가 짝수면 몫*2로 딱 나누어떨어지지만
    // 지수가 홀수면 몫 , 몫+1이 되므로 짝수에서 값을 한번더 곱해준다.
    if k % 2 == 0 {
        return m*m%p
    } else {
        return (m*m%p)*n%p
    }
    
}
// 출력
print(factorial(N) * divide(factorial(N-K) * factorial(K) % p, p-2) % p)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-11444 피보나치 수 6 Swift  (0) 2024.05.05
BOJ-10830 행렬 제곱 Swift  (0) 2024.05.05
BOJ-1629 곱셈 Swift  (0) 2024.05.03
BOJ-1780 종이의 개수 Swift  (0) 2024.05.03
BOJ-1992 쿼드트리 Swift  (0) 2024.05.03

문제

자연수 A를 B번 곱한 수를 알고 싶다. 단 구하려는 수가 매우 커질 수 있으므로 이를 C로 나눈 나머지를 구하는 프로그램을 작성하시오.

입력

첫째 줄에 A, B, C가 빈 칸을 사이에 두고 순서대로 주어진다. A, B, C는 모두 2,147,483,647 이하의 자연수이다.

출력

첫째 줄에 A를 B번 곱한 수를 C로 나눈 나머지를 출력한다.

내가 푼 풀이

접근방법: 수학, 분할정복

이 문제는 지수법칙과 모듈러연산의 분배법칙을 알고있다면 풀 수있다.

본인은 몰라서 검색을 통해 알게되었다..

 

지수법칙:  $x^{ab} = x^{a} \times x^{b}$

모듈러연산의 분배법칙:$(A\times B)\mathrm{mod}C = (A \mathrm{mod}C\times B\mathrm{mod}C)\mathrm{mod}C$

 

분할: 지수법칙 이용

$m = B / 2$

B가 짝수: $A^{C} = A^{m} \times A^{m} $

B가 홀수:$A^{C} = A^{m} \times A^{m+1} $

 

정복: 분배법칙 이용

$ B \doteq 1 $  $\mathrm{return} $  $A\div C $

$B\neq 1$ $((A^{m} \div C) \times (A^{m}\div C)) \div C $ , $((A^{m} \div C) \times (A^{m+1}\div C)) \div C $

B가 1이 아니라면 두가지 경우의 수가 있다.(홀수, 짝수)

 

이렇게 m이 1이될때까지 분할후 m이 1이되면 정복과정을 진행하여 결과를 도출한다.

 

직관적으로 코드를 짰다가 함수 재귀호출을 너무많이하여 시간초과가 났다.

<시간초과>

// 시간초과
func mul2(_ n: Int,_ c: Int,_ d: Int) -> Int {
    if c == 1 { return n%d}
    let m = c/2
    if c % 2 == 0{
        return (mul(n, m, d)%d * mul(n, m, d)%d)%d
    } else {
        return (mul(n, m, d)%d * mul(n, m+1, d)%d)%d
    }
    
}

 

<수정한 코드>

import Foundation

// 입력
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
var A = input[0], B = input[1], C = input[2]

// 분할 정복
func mul(_ n: Int,_ c: Int,_ d: Int) -> Int {
    
    // c가 1이될때까지 분할하고, 1이됬다면 정복과정을 진행한다.
    if c == 1 { return n%d}
    
    // 지수법칙을 이용하여 c/2번 곱한 나머지를 구한다.
    let m = mul(n, c/2, d)
    
    // c가 짝수라면 곱해주고,
    // c가 홀수라면 c = 5 -> n^2 * n^2 * n^1
    // n을 한번곱한 나머지를 추가로 곱해준다.(분배법칙)
    if c % 2 == 0{
        return m*m%d
    } else {
        return m*m%d*n%d
    }
    
}
print(mul(A, B, C))

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-10830 행렬 제곱 Swift  (0) 2024.05.05
BOJ-11401 이항 계수 3 Swift  (0) 2024.05.03
BOJ-1780 종이의 개수 Swift  (0) 2024.05.03
BOJ-1992 쿼드트리 Swift  (0) 2024.05.03
BOJ-10986 나머지 합 Swift  (1) 2024.05.02

문제

N×N크기의 행렬로 표현되는 종이가 있다. 종이의 각 칸에는 -1, 0, 1 중 하나가 저장되어 있다. 우리는 이 행렬을 다음과 같은 규칙에 따라 적절한 크기로 자르려고 한다.

  1. 만약 종이가 모두 같은 수로 되어 있다면 이 종이를 그대로 사용한다.
  2. (1)이 아닌 경우에는 종이를 같은 크기의 종이 9개로 자르고, 각각의 잘린 종이에 대해서 (1)의 과정을 반복한다.

이와 같이 종이를 잘랐을 때, -1로만 채워진 종이의 개수, 0으로만 채워진 종이의 개수, 1로만 채워진 종이의 개수를 구해내는 프로그램을 작성하시오.

입력

첫째 줄에 N(1 ≤ N ≤ 37, N은 3k 꼴)이 주어진다. 다음 N개의 줄에는 N개의 정수로 행렬이 주어진다.

출력

첫째 줄에 -1로만 채워진 종이의 개수를, 둘째 줄에 0으로만 채워진 종이의 개수를, 셋째 줄에 1로만 채워진 종이의 개수를 출력한다.

내가 푼 풀이

접근방법: 분할정복

1. 주어진 배열이 모두 같은원소로 이루어졌는지 판단

2. 이루어지지 않았다면 해당배열을 같은크기의 배열 9등분으로 나눔

3. 모두 같은원소로 이루어질때 까지 1-2 반복(재귀호출)

4. 같은원소로 이루어졌다면 해당 원소에따라 다르게 저장

5. 저장된 값을 출력

 

코드로 구현하면 다음과 같다.

import Foundation

// 입력
let N = Int(readLine()!)!
var arr = [[String]]()
arr.append(["0"])
var result = [0,0,0]
for i in 0..<N {
    var a = readLine()!.split(separator: " ").map{String($0)}
    a.insert("0", at: 0)
    arr.append(a)
}

// 분할
func divide(_ xs: Int,_ xe: Int,_ ys: Int,_ ye: Int,_ arr: [[String]],_ N: Int) {
    if !check(xs, xe, ys, ye, arr) {
        let m = N/3
        // 9가지로 분할, 해당 분할 위치에따른 재귀호출
        // [1, 2, 3]
        // [4, 5, 6]
        // [7, 8, 9]
        
        // 1
        divide(xs, xs+m-1, ys, ys+m-1, arr, N/3)
        // 2
        divide(xs+m, xs+m*2-1, ys, ys+m-1, arr, N/3)
        // 3
        divide(xs+m*2, xe, ys, ys+m-1, arr, N/3)
        // 4
        divide(xs, xs+m-1, ys+m, ys+m*2-1, arr, N/3)
        // 5
        divide(xs+m, xs+m*2-1, ys+m, ys+m*2-1, arr, N/3)
        // 6
        divide(xs+m*2, xe, ys+m, ys+m*2-1, arr, N/3)
        // 7
        divide(xs, xs+m-1, ys+m*2, ye, arr, N/3)
        // 8
        divide(xs+m, xs+m*2-1, ys+m*2, ye, arr, N/3)
        // 9
        divide(xs+m*2, xe, ys+m*2, ye, arr, N/3)
    }
}

// 같은 원소로 이루어졌는지 확인
func check(_ xs: Int,_ xe: Int,_ ys: Int,_ ye: Int,_ arr: [[String]]) -> Bool{
    let val = arr[ys][xs]
    for i in ys...ye {
        for j in xs...xe {
            if val != arr[i][j] { return false }
        }
    }
    // 해당 원소의 따라 다르게 저장
    switch val {
    case "-1": result[0] += 1
    case "0": result[1] += 1
    case "1": result[2] += 1
    default:
        break
    }
    return true
}

// 실행 및 출력
divide(1, N, 1, N, arr, N)
print("\(result[0])\n\(result[1])\n\(result[2])\n")

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-11401 이항 계수 3 Swift  (0) 2024.05.03
BOJ-1629 곱셈 Swift  (0) 2024.05.03
BOJ-1992 쿼드트리 Swift  (0) 2024.05.03
BOJ-10986 나머지 합 Swift  (1) 2024.05.02
BOJ-11659 구간 합 구하기 4 Swift  (1) 2024.05.01

문제

흑백 영상을 압축하여 표현하는 데이터 구조로 쿼드 트리(Quad Tree)라는 방법이 있다. 흰 점을 나타내는 0과 검은 점을 나타내는 1로만 이루어진 영상(2차원 배열)에서 같은 숫자의 점들이 한 곳에 많이 몰려있으면, 쿼드 트리에서는 이를 압축하여 간단히 표현할 수 있다.

주어진 영상이 모두 0으로만 되어 있으면 압축 결과는 "0"이 되고, 모두 1로만 되어 있으면 압축 결과는 "1"이 된다. 만약 0과 1이 섞여 있으면 전체를 한 번에 나타내지를 못하고, 왼쪽 위, 오른쪽 위, 왼쪽 아래, 오른쪽 아래, 이렇게 4개의 영상으로 나누어 압축하게 되며, 이 4개의 영역을 압축한 결과를 차례대로 괄호 안에 묶어서 표현한다

위 그림에서 왼쪽의 영상은 오른쪽의 배열과 같이 숫자로 주어지며, 이 영상을 쿼드 트리 구조를 이용하여 압축하면 "(0(0011)(0(0111)01)1)"로 표현된다. N ×N 크기의 영상이 주어질 때, 이 영상을 압축한 결과를 출력하는 프로그램을 작성하시오.

입력

첫째 줄에는 영상의 크기를 나타내는 숫자 N 이 주어진다. N 은 언제나 2의 제곱수로 주어지며, 1 ≤ N ≤ 64의 범위를 가진다. 두 번째 줄부터는 길이 N의 문자열이 N개 들어온다. 각 문자열은 0 또는 1의 숫자로 이루어져 있으며, 영상의 각 점들을 나타낸다.

출력

영상을 압축한 결과를 출력한다.

내가 푼 풀이

접근방법: 분할정복

1. 처음에 주어진 배열이 모두 같은원소로 이루어졌는지 확인

2. 같은원소로 이루어지지 않았다면 해당배열을 4등분

3. 재귀호출로 1-2번 반복하다가 같은원소로 이루어졌다면 해당배열의 가장 왼쪽위 원소를 출력

4. 원소를 출력할때는 압축이 되었으므로 괄호 ( )를 감싸서 출력한다.

 

 

코드로 구현하면 다음과 같다.

import Foundation

// 입력
let N = Int(readLine()!)!
var arr = [[Character]]()
arr.append(["0"])
for i in 0..<N {
    var a = Array(readLine()!)
    a.insert("0", at: 0)
    arr.append(a)
}

// 분할
func divide(_ xs: Int,_ xe: Int,_ ys: Int,_ ye: Int,_ arr: [[Character]]) -> String {
    var s = "\(arr[ys][xs])"
    // 같은원소로 이루어지지 않았다면 4등분
    if !check(xs,xe,ys,ye,arr) {
        let xm = (xs+xe)/2
        let ym = (ys+ye)/2
        // 압축하는과정이므로 괄호로 감싼다.
        s = "(\(divide(xs, xm, ys, ym, arr))\(divide(xm+1, xe, ys, ym, arr))\(divide(xs, xm, ym+1, ye, arr))\(divide(xm+1, xe, ym+1, ye, arr)))"
    }
    // 같은 원소로 이루어졌다면 배열의 왼쪽위 원소 출력
    return s
}

// 해당 NxN배열이 모두 같은원소로 이루어졌는지 확인
func check(_ xs: Int,_ xe: Int,_ ys: Int,_ ye: Int,_ arr: [[Character]]) -> Bool {
    let color = arr[ys][xs]
    for i in ys...ye {
        for j in xs...xe {
            if color != arr[i][j] { return false }
        }
    }
    return true
}
print(divide(1, N, 1, N, arr))

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-1629 곱셈 Swift  (0) 2024.05.03
BOJ-1780 종이의 개수 Swift  (0) 2024.05.03
BOJ-10986 나머지 합 Swift  (1) 2024.05.02
BOJ-11659 구간 합 구하기 4 Swift  (1) 2024.05.01
14725-개미굴 Swift  (0) 2024.04.30

문제

수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오.

즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j) 쌍의 개수를 구해야 한다.

입력

첫째 줄에 N과 M이 주어진다. (1 ≤ N ≤ 106, 2 ≤ M ≤ 103)

둘째 줄에 N개의 수 A1, A2, ..., AN이 주어진다. (0 ≤ Ai ≤ 109)

출력

첫째 줄에 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 출력한다.

내가 푼 풀이

첫번째 접근방법: 세그먼트 트리(시간초과)

구간합트리로 해당 구간합을 구해 나누어 떨어지는지 확인해보았다.

구간합트리의 모든노드는 모든 구간합의 경우의수가 아니였고, 구간합트리를 만들어도 모든 구간합의 경우의수를 구하려니 2중 반복문으로 시간초과가 나게되었다.

 

이 문제는 수학적지식을 이용해서 풀어야만 했었다. 지식의 한계를 느끼고 다른사람의 해석을 보았다.

 

두번째 접근방법: 합배열과 구간합

구간합배열은 이렇게 만들 수 있다.

S(1), S(2), S(3)....

S(1)은 1부터1까지, S(2)는 1부터2까지, S(3)은 1부터3까지의 구간합

  A[1] A[2] A[3] A[4] A[5]
A 1 2 3 1 2
S 1 3 6 7 9
S[i] % M 1 0 0 1 0

 

여기서 구간합배열인 S를 M으로 나누어보면 아래 배열과 같다 [1, 0, 0, 1, 0]

이를 해석해보면 구간합 [1~2], [1~3], [1~5]는 M과 나누어떨어지고, [1~1], [1~4]는 나머지 1이 남는다.

또한 구간합은 S[i~j] = S[j] - S[i-1]로 나타낼 수 있다. 예로 2에서4까지의 구간합은 S[4] - S[1]이다.

이 특징을 이용하면 S[4] - S[1] = 1 - 1 = 0이되고 이는 M으로 나누어떨어진다고 볼 수 있다.

따라서 우리는 아래처럼 종합할 수 있다.

모든 구간합의 경우의수는 아래와 같다.

1. 구간합배열S를 M으로 나누었을때의 0이되는 구간합

2. 나머지배열중 서로 나머지가 같아서 S[i~j] = S[j] - S[i-1] = 0이되는 두개의 나머지배열

 

1번의 경우의수는 S[2], S[3], S[5] 총 3개가 된다.

2번의 경우의 수는 같은 나머지원소중에서 2개를 뽑으면된다(S[j] - S[i-1] = 0을 성립하기 위한 S[j]와 S[i-1]이 필요하다.)

나머지가 0인것중에서 2개를 뽑는 수: ${_3}C{_2}$ -> 3*(3-1)/2 = 3

나머지가 1인것중에서 2개를 뽑는 수: ${_2}C{_2}$  -> 2*(2-1)/2 = 1

3 + 3 + 1 = 7 이 된다.

 

M으로 나누었을때 나머지는 0부터 M-1까지 있으므로 모두 순회하며 더해준다.

 

코드로 구현하면 다음과 같다.

import Foundation

// 입력받기
let input = readLine()!.split(separator: " ").map{Int(String($0))!}
let N = input[0], M = input[1]
// 주어진배열
var arr = readLine()!.split(separator: " ").map{Int(String($0))!}
// 합배열
var S = [Int]()
var sum = 0
// 합배열을 M으로 나눈 나머지배열
var divided = [Int]()
// 나누어떨어지는 갯수
var count = 0

// 합배열 만들기
for i in arr {
    sum += i
    S.append(sum)
}

// 나머지 배열만들기
divided = S.map{$0 % M}
// 나머지 배열중 나머지가 0이면 그 구간합은 나누어떨어지므로 더해준다.
count = divided.filter{$0 == 0}.count

// 나머지배열중 같은 나머지의 갯수중에 2개를 고르는 경우의수를 더한다.
// S[i~j] = S[j] - S[i-1] = 0이 되는 두개의 구간합이 필요하므로
for i in 0..<M {
    let num = divided.filter{$0 == i}.count
    count = count + (num * (num-1))/2
}
print(count)

'코딩테스트 > 백준' 카테고리의 다른 글

BOJ-1780 종이의 개수 Swift  (0) 2024.05.03
BOJ-1992 쿼드트리 Swift  (0) 2024.05.03
BOJ-11659 구간 합 구하기 4 Swift  (1) 2024.05.01
14725-개미굴 Swift  (0) 2024.04.30
BOJ-1043 거짓말 Swift  (0) 2024.04.29

+ Recent posts