알고리즘 모음(C++)

백준 16118 - 달빛 여우(C++) 본문

백준

백준 16118 - 달빛 여우(C++)

공대생의 잡다한 사전 2024. 2. 4. 22:33

문제 링크입니다. https://www.acmicpc.net/problem/16118

16118번: 달빛 여우

첫 줄에 나무 그루터기의 개수와 오솔길의 개수를 의미하는 정수 N, M(2 ≤ N ≤ 4,000, 1 ≤ M ≤ 100,000)이 주어진다. 두 번째 줄부터 M개의 줄에 걸쳐 각 줄에 세 개의 정수 a, b, d(1 ≤ a, b ≤ N, a ≠ b

www.acmicpc.net

다익스트라를 사용하는 문제였습니다.

달빛 여우와 늑대가 어느 정점을 갈 때의 최소 비용이 여우가 더 적은 정점의 개수를 찾는 문제입니다.
주의할 점은 여우는 주어진 비용으로만 가지만, 늑대는 (비용/2) -> (비용*2) -> (비용/2) ..... 의 방법으로 가게 됩니다.

그렇다면 다른 정점까지의 여우의 최솟값, 늑대의 최솟값을 구한 뒤 이들을 비교해 답을 구해주면 됩니다.

여우의 최소 비용을 구하는 방법은 일반적인 다익스트라를 통해서 쉽게 구할 수 있습니다.
문제는 늑대의 최소 비용을 구하는 방법인데, 간단하게 생각하면은 한번은 /2 를 하고 다음번은 *2를 하는 방법을 하면 된다고 생각할 수 있습니다.

물론, /2 하고 *2를 하는 방법은 같지만, 생각해 봐야할 것이 있습니다.

해당 그래프를 본다면, 1->4까지의 경우가 문제가 됩니다.
늑대의 최소 비용은 7이 됩니다.
그렇지만, 정점의 최소 비용만을 가지고 찾게 된다면, 9.5라는 값을 저장하게 됩니다.
이는 1 -> 2가지 갔을 때의 최소 값은 1.5 이기 떄문에, 1 -> 4까지 가는 방법인 (1 -> 3 -> 2 -> 4)에서 1 -> 2의 값인 5를 저장하기 않기 때문입니다.
이를 해결하기 위해서는 어떤 정점을 들릴 때, 경우가 2가지 임을 찾을 수 있어야 합니다.
어떤 정점은 *2로 왔을 때, /2로 왔을 때, 2번이 있기에, 각각의 경우에 따라서 다른 최소 비용을 가지게 될 수 있습니다.
따라서, 배열을 [2][N]으로 만들어 2가지 경우의 최소 비용을 저장해주면 됩니다.


저는 문제를 풀 때, 2로 나눌 경우 소수가 나올 수 있기에 정점의 비용을 저장할 때, 2를 곱해서 저장해줬으며,
최소 비용을 저장한 배열은 [2][2][N]으로 만들어 여우 or 늑대 + (*2 or /2) + 정점으로 만들어 줬습니다.




자세한 것은 코드를 참고해주세요.

#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <iostream>
#include <algorithm>
#include <queue>
#include <cmath>
#include <cstdio>
#include <climits>
#define INF LLONG_MAX
#define F first
#define S second

using namespace std;

int N, M;
vector<pair<int, int>> connect[4001];
long long Distance[2][2][4001]; // (여우 / 늑대), 이동횟수(늑대만 사용), 정점
priority_queue<pair<long long, pair<int, int>>> q; //거리, 이동 횟수, 정점

void reset_distance(){
	for(int i = 0; i < 2; i++){
		for(int j = 1; j <= N; j++){
			Distance[i][0][j] = INF;
			Distance[i][1][j] = INF;
		}
	}
}

void Dijstra(int way){
	Distance[way][0][1] = 0;
	q.push({-0, {0, 1}});
	while(!q.empty()){
		int x = q.top().S.S;
		int num = q.top().S.F;
		long long cost = -q.top().F;
		q.pop();
		if(way == 0 && Distance[way][0][x] < cost) continue;
		if(way == 1 && Distance[way][num%2][x] < cost) continue;
		for(int i = 0; i < connect[x].size(); i++){
			int xx = connect[x][i].F;
			if(way == 0){
				long long n_cost = cost + connect[x][i].S;
				if(Distance[way][0][xx] > n_cost){
					Distance[way][0][xx] = n_cost;
					q.push({-Distance[way][0][xx], {num+1, xx}});
				}	
			}
			else{
				if(num % 2 == 0){ // 2배만큼 빠르게 갈 때
					long long n_cost = cost + connect[x][i].S / 2;
					if(Distance[way][(num+1)%2][xx] > n_cost){
						Distance[way][(num+1)%2][xx] = n_cost;
						q.push({-Distance[way][(num+1)%2][xx], {num+1, xx}});
					}	
				}	
				else{ // 2배만큼 느리게 갈 때
					long long n_cost = cost + connect[x][i].S * 2;
					if(Distance[way][(num+1)%2][xx] > n_cost){
						Distance[way][(num+1)%2][xx] = n_cost;
						q.push({-Distance[way][(num+1)%2][xx], {num+1, xx}});
					}	
				}
			}
		}
	}
}

void solve(){
	reset_distance();
	Dijstra(0);
	Dijstra(1);
	int ans = 0;
	for(int i = 1; i <= N; i++) {
		if(Distance[0][0][i] < min(Distance[1][0][i], Distance[1][1][i])){
			ans++;
		}
	}
	cout << ans;
}

int main(){
	cin.tie(0);
	cout.tie(0);
	cin >> N >> M;
	for(int i = 1; i <= M; i++){
		int x, y, cost;
		cin >> x >> y >> cost;
		connect[x].push_back({y, cost * 2});
		connect[y].push_back({x, cost * 2});
	}	
	solve();
	return 0;
}


질문 및 조언은 코드를 참고해주세요.

'백준' 카테고리의 다른 글

백준 5063 - TGN(C++)  (0) 2024.02.04
백준 1267 - 핸드폰 요금(C++)  (0) 2024.02.04
백준 2645 - 회로배치(C++)  (0) 2024.02.04
백준 2398 - 3인통화(C++)  (1) 2024.01.29
백준 1884 - 고속도로(C++)  (2) 2024.01.28