1446 swift / c++ - 지름길

2024. 10. 4. 07:42🐣/BOJ

다이나믹프로그래밍과 다익스트라 두 방법 모두 사용할 수 있습니다.

 

시간 복잡도

  • 시간 복잡도는 O(D + N)입니다.
    • D는 고속도로의 길이, N은 지름길의 수입니다.
    • 문제에서 N ≤ 12, D ≤ 10,000이므로, 이 접근 방식은 충분히 효율적입니다.

 

C++ DP 풀이.

#include <bits/stdc++.h>

using namespace std;

#define INF 1e9

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n, d;
  cin >> n >> d;

  vector<pair<int, int>> graph[10001];

  for (int i = 0; i < n; i++) {
    int from, to, cost;
    cin >> from >> to >> cost;
    if (to <= d) {
      graph[from].push_back({to, cost});
    }
  }

  vector<int> dist(d + 1, INF);

  dist[0] = 0;

  for (int i = 0; i <= d; i++) {
    if (i > 0) {
      dist[i] = min(dist[i], dist[i - 1] + 1);
    }

    for (auto& g : graph[i]) {
      int next = g.first;
      int cost = g.second;
      if (dist[next] > dist[i] + cost) {
        dist[next] = dist[i] + cost;
      }
    }
  }

  cout << dist[d];

  return 0;
}
import Foundation

let nd = readLine()!.split(separator: " ").map{Int(String($0))!}
let (n, d) = (nd[0], nd[1])

var graph = [[(Int, Int)]](repeating: [], count: d + 1)

for _ in 0..<n {
    let shortcut = readLine()!.split(separator: " ").map{Int(String($0))!}
    let from = shortcut[0], to = shortcut[1], cost = shortcut[2]
    if to <= d {
        graph[from].append((to, cost))
    }
}

var dist = [Int](repeating: Int.max, count: d + 1)
dist[0] = 0

for i in 0...d {
    if i > 0 {
        dist[i] = min(dist[i], dist[i - 1] + 1)
    }
    
    for (next, cost) in graph[i] {
        if next <= d {
            dist[next] = min(dist[next], dist[i] + cost)
        }
    }
}

print(dist[d])

다익스트라 알고리즘과 DP의 차이점

  • 동적 계획법 (DP) 방식은 각 지점에서 순차적으로 거리를 갱신하면서 최단 거리를 찾습니다. 이 방식은 모든 지점을 방문하며 각 지름길을 업데이트하는 방식으로 구현됩니다.
  • 다익스트라 알고리즘우선순위 큐를 사용하여 현재 최단 거리가 작은 지점부터 탐색합니다. 따라서 최적의 경로를 더 빨리 찾기 위한 탐색 순서의 최적화가 가능합니다.
  • 이 문제에서 두 방법 모두 사용이 가능하지만, 다익스트라 알고리즘은 일반적으로 더 많은 정점이 존재하고 가중치가 다양한 경우 더 효율적으로 작동합니다.

시간 복잡도

  • 다익스트라 알고리즘의 시간 복잡도는 O((V + E) * log V)입니다.
    • V는 정점의 개수 (D), E는 간선의 개수 (N)입니다.
    • 우선순위 큐를 사용하는 경우, 각 정점 및 간선의 업데이트가 로그 시간에 수행됩니다.
  • 이 문제에서는 N ≤ 12, D ≤ 10,000이기 때문에, 다익스트라의 시간 복잡도는 충분히 효율적입니다.

결론

  • DP 방식과 다익스트라 알고리즘 모두 이 문제에서 사용될 수 있습니다.
  • DP 방식은 각 위치에서 가능한 모든 경로를 반복적으로 갱신하는 방식이며, 다익스트라 알고리즘은 우선순위 큐를 사용하여 현재 가능한 최단 경로를 효율적으로 찾아갑니다.
  • 다익스트라 방식은 더 많은 정점과 간선이 있을 때 효율적으로 작동하며, 우선순위 큐를 사용하여 최적의 정점을 빠르게 선택합니다.

c++ 다익스트라 풀이

#include <bits/stdc++.h>

using namespace std;

#define INF 1e9

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);

  int n, d;
  cin >> n >> d;

  vector<pair<int, int>> graph[10001];

  for (int i = 0; i < n; i++) {
    int from, to, cost;
    cin >> from >> to >> cost;
    if (to <= d) {
      graph[from].push_back({to, cost});
    }
  }

  // 다익스트라를 위한 최소 힙 선언 (거리, 정점) 형태로 저장
  priority_queue<pair<int, int>, vector<pair<int, int>>,
                 greater<pair<int, int>>>
      pq;

  // 최단 거리 배열 초기화
  vector<int> dist(d + 1, INF);
  dist[0] = 0;
  pq.push({0, 0});  // (거리, 시작 위치)

  // 다익스트라 알고리즘
  while (!pq.empty()) {
    int currentDist = pq.top().first;
    int currentNode = pq.top().second;
    pq.pop();

    // 이미 처리된 거리보다 큰 경우는 무시
    if (currentDist > dist[currentNode]) {
      continue;
    }

    // 직선 도로로 이동 (다음 1km)
    if (currentNode + 1 <= d && dist[currentNode + 1] > currentDist + 1) {
      dist[currentNode + 1] = currentDist + 1;
      pq.push({dist[currentNode + 1], currentNode + 1});
    }

    // 현재 노드에서 출발하는 지름길 처리
    for (auto& shortcut : graph[currentNode]) {
      int nextNode = shortcut.first;
      int shortcutCost = shortcut.second;

      if (dist[nextNode] > currentDist + shortcutCost) {
        dist[nextNode] = currentDist + shortcutCost;
        pq.push({dist[nextNode], nextNode});
      }
    }
  }

  cout << dist[d] << "\n";
  return 0;
}

Swift Priority Queue

더보기
struct Heap<T> {
    var elements: [T] = []
    let priorityFunction: (T, T) -> Bool
    
    init(priorityFunction: @escaping (T, T) -> Bool) {
        self.priorityFunction = priorityFunction
    }
    
    var isEmpty: Bool {
        return elements.isEmpty
    }
    
    func peek() -> T? {
        return elements.first
    }
    
    mutating func remove() -> T? {
        guard !elements.isEmpty else {
            return nil
        }
        if elements.count == 1 {
            return elements.removeFirst()
        } else {
            let value = elements.first
            elements[0] = elements.removeLast()
            siftDown(elementAtIndex: 0)
            return value
        }
    }
    
    mutating func push(_ element: T) {
        elements.append(element)
        siftUp(elementAtIndex: elements.count - 1)
    }
    
    mutating func pop() -> T? {
        guard !isEmpty else {
            return nil
        }
        elements.swapAt(0, elements.count - 1)
        let poppedElement = elements.removeLast()
        siftDown(elementAtIndex: 0)
        return poppedElement
    }
    
    private mutating func siftUp(elementAtIndex index: Int) {
        let parent = parentIndex(of: index)
        guard !isRoot(index), priorityFunction(elements[index], elements[parent]) else {
            return
        }
        elements.swapAt(index, parent)
        siftUp(elementAtIndex: parent)
    }
    
    private mutating func siftDown(elementAtIndex index: Int) {
        let childIndex = highestPriorityIndex(for: index)
        if index == childIndex {
            return
        }
        elements.swapAt(index, childIndex)
        siftDown(elementAtIndex: childIndex)
    }
    
    private func isRoot(_ index: Int) -> Bool {
        return index == 0
    }
    
    private func leftChildIndex(of index: Int) -> Int {
        return 2 * index + 1
    }
    
    private func rightChildIndex(of index: Int) -> Int {
        return 2 * index + 2
    }
    
    private func parentIndex(of index: Int) -> Int {
        return (index - 1) / 2
    }
    
    private func highestPriorityIndex(for parent: Int) -> Int {
        let leftChild = leftChildIndex(of: parent)
        let rightChild = rightChildIndex(of: parent)
        
        var highest = parent
        if leftChild < elements.count && priorityFunction(elements[leftChild], elements[highest]) {
            highest = leftChild
        }
        if rightChild < elements.count && priorityFunction(elements[rightChild], elements[highest]) {
            highest = rightChild
        }
        return highest
    }
}

Swift 다익스트라

더보기
let nd = readLine()!.split(separator: " ").map{Int(String($0))!}
let (n, d) = (nd[0], nd[1])

var graph = [[(Int, Int)]](repeating: [], count: d + 1)

for _ in 0..<n {
    let shortcut = readLine()!.split(separator: " ").map{Int(String($0))!}
    let from = shortcut[0], to = shortcut[1], cost = shortcut[2]
    if to <= d {
        graph[from].append((to, cost))
    }
}

var dist = [Int](repeating: Int.max, count: d + 1)
dist[0] = 0

var pq = Heap<(Int, Int)>(priorityFunction: {$0.0 < $1.0})
pq.push((0, 0))

while !pq.isEmpty {
    let (currentDist, currentNode) = pq.pop()!
    
    if currentDist > dist[currentNode] {
        continue
    }
    
    if currentNode + 1 <= d && dist[currentNode + 1] > currentDist + 1 {
        dist[currentNode + 1] = currentDist + 1
        pq.push((dist[currentNode + 1], currentNode + 1))
    }
    
    for (nextNode, cost) in graph[currentNode] {
        if dist[nextNode] > currentDist + cost {
            dist[nextNode] = currentDist + cost
            pq.push((dist[nextNode], nextNode))
        }
    }
}

print(dist[d])

'🐣 > BOJ' 카테고리의 다른 글

16235 c++ - 나무 재테크  (12) 2024.10.08
1299 c++ - 전쟁-탈출편2  (0) 2024.10.05
18352 swift - 특정 거리의 도시 찾기  (0) 2024.10.02
12100 swift / c++ - 2048 (Easy)  (0) 2024.09.23
6603 swift - 로또  (0) 2024.04.28