[풀이] 1에서 출발해서 N까지 도착할때 필요한 최소 레벨을 구하는 문제입니다. 다익스트라를 응용해서 풀 수 있습니다. 일반적인 다익스트라의 경우 [현재 노드 cur 까지 올때까지 필요했던 cost] + [다음 노드 nxt까지 가는데 필요한 cost] 를 더해주어 이 값이 현재 dist[nxt]의 값보다 작다면 이 값으로 dist[nxt]를 업데이트 해주는 방식으로 풀지만 이 문제의 경우 [현재 노드 cur 까지 올때까지 필요한 최소 level]과 [다음 노드 nxt까지 가는데 필요한 level] 의 최댓값과 level[nxt]를 비교해서 업데이트 해주면 됩니다. 왜냐하면 nxt까지 가는데 둘중 더 높은 레벨만큼은 반드시 필요하기 때문입니다.
또한 문제의 조건에 따라 최종노드 N까지 가는데 필요한 level보다 크면서 가장 작은 소수를 구해야 하므로 level[N]보다 큰 첫번째 소수를 구해서 출력해주면 정답이 됩니다.
[풀이] 트랩에는 연결된 간선이 역방향인 경우와 정방향인 경우 두가지 상태가 있습니다. 간선이 역방향이려면 트랩에 홀수번 방문한 상태여야 하며, 정방향인 경우는 방문을 안했거나 짝수번 방문한 상태입니다.
트랩의 개수가 최대 10개밖에 되지 않으므로 10개의 비트로 아래와 같이 트랩의 상태를 저장할 수 있습니다. 0000000000(0)~1111111111(2^10-1) (0인 경우 정방향, 1인 경우 역방향)
이를 이용해 vector<pair<int,int>> adj[1025][1001] 형태의 adj[state][node] 2차원 인접리스트를 선언한 뒤 state에 따라 올바른 간선 방향을 가지는 인접 리스트를 각각 만들어줍니다.
트랩의 개수가 k개라면 인접 리스트는 총 2^k개가 나올 수 있습니다. 만약 어떤 노드 u,v를 연결하는 간선이 입력으로 주어질 때, i) u,v가 모두 정방향인 경우(둘다 트랩이 아니거나, 트랩이어도 state 비트마스크에서 두 트랩 모두 0) u,v는 u->v 방향 그대로 저장
ii) u,v 둘중 하나가 트랩이면서 역방향인 경우 간선을 뒤집어야 하므로 v->u 방향으로 저장
iii) u,v 모두 트랩이면서 역방향인 경우 이 경우에는 간선이 u에 의해 한번 뒤집히고, v에 의해 또한번 뒤집히므로 결국 정방향인 상태가 됩니다. 그대로 u->v 방향으로 저장
위와 같이 state에 따라서 간선이 역방향, 정방향인지 정할 수 있고 각 state마다 인접 리스트(그래프)가 생성되게 됩니다.
이를 이용해 int dist[1025][1001] 배열을 선언하여 dist[state][node] : 현재 트랩의 상태가 state 일때, node까지의 최단 거리를 업데이트 해주는 BFS를 이용하여 end node까지의 거리를 구할 수 있습니다. 시간 제한이 10초로 넉넉하여 다익스트라를 사용하지 않고 일반 BFS를 사용하여도 AC를 받았습니다.
현재 노드가 cur 일때, 연결된 다음 노드 nxt가 트랩이라면 nxt에 연결된 간선이 뒤집어져야 하므로 state 비트를 변경해주어야 합니다. 간단히 bit xor 연산으로 다음 state를 구할 수 있습니다. 현재 state가 bit이고 nxt가 i번째 trap 이라면 next_bit = bit ^ (1<<i) 이 됩니다. i번째 bit만 뒤집어주면 되기 때문입니다.
다익스트라로 구현하면 end 노드에 처음 도착한 순간이 최단거리 이지만 BFS로 구현하였기 때문에 항상 최적의 경로로 방문하는 것이 아니어서 더이상 방문할 노드가 없을 때까지 계속 방문하며 최솟값을 업데이트 해주어야 합니다.
#include <string>
#include <vector>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <map>
using namespace std;
int dist[1025][1001];
bool trap[1001];
map<int,int> trapidx;
vector<pair<int,int>> adj[1025][1001];
bool isFlip(int a,vector<int>& flag){
return trap[a] && flag[trapidx[a]];
}
int solution(int n, int start, int end, vector<vector<int>> roads, vector<int> traps) {
for(int i=0;i<traps.size();i++) {
trap[traps[i]]=1;
trapidx[traps[i]]=i;
}
for(int i=0;i<(1<<traps.size());i++){
vector<int> flag(traps.size(),0);
for(int j=1;j<=n;j++) dist[i][j]=9e8;
for(int j=0;j<traps.size();j++) {
if(i&(1<<j)) flag[j]=1;
}
for(auto& r : roads){
int u=r[0],v=r[1],c=r[2];
int cnt = isFlip(u,flag)+isFlip(v,flag);
if(cnt==1) adj[i][v].push_back({u,c});
else adj[i][u].push_back({v,c});
}
}
queue<pair<int,int>> q;
dist[0][start]=0;
q.push({0,start});
int ans=9e8;
while(!q.empty()){
auto [bit,cur] = q.front(); q.pop();
if(cur==end) ans=min(ans,dist[bit][cur]);
for(auto [nxt,d] : adj[bit][cur]){
int nb = bit;
if(trap[nxt]) {
int i = trapidx[nxt];
nb ^= (1<<i);
}
if(dist[nb][nxt] > dist[bit][cur]+d){
dist[nb][nxt]=dist[bit][cur]+d;
q.push({nb,nxt});
}
}
}
return ans;
}
[풀이] 모든 집에 대해 다익스트라를 돌리면 시간초과가 나오기 때문에 모든 맥도날드와 cost 0으로 연결된 더미 노드, 모든 스타벅스와 cost 0으로 연결된 더미 노드를 추가 한 뒤, 각각 더미 노드에서 다익스트라를 돌리면 각 집에서 맥도날드 까지의 거리, 스타벅스 까지의 거리를 1차원 배열에 각각 저장할 수 있다. 이 두 배열을 가지고 조건 만족하는 최단 거리를 구해주면 된다.
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <functional>
using namespace std;
using ll = long long;
int V,E,M,X,Y;
vector<pair<int,int>> adj[10002];
ll dist[10002],md[10002],ans=1e12;
bool mac[10002],star[10002];
void dijk(int n){
for(int i=0;i<=V+1;i++) dist[i]=1e12;
priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>> pq;
pq.push({0,n});
dist[n]=0;
while(!pq.empty()){
auto qt = pq.top(); pq.pop();
if(dist[qt.second] < qt.first) continue;
for(auto p : adj[qt.second]){
ll nxt = p.first;
int cost = p.second;
if(qt.first+cost<dist[nxt]){
dist[nxt]=qt.first+cost;
pq.push({qt.first+cost,nxt});
}
}
}
}
int main(){
scanf("%d%d",&V,&E);
while(E--){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
adj[u].push_back({v,w});
adj[v].push_back({u,w});
}
scanf("%d%d",&M,&X);
while(M--){
int v;
scanf("%d",&v);
mac[v]=1;
adj[0].push_back({v,0});
}
scanf("%d%d",&M,&Y);
while(M--){
int v;
scanf("%d",&v);
star[v]=1;
adj[V+1].push_back({v,0});
}
dijk(0);
for(int i=0;i<=V+1;i++) md[i]=dist[i];
dijk(V+1);
for(int i=1;i<=V;i++){
if(mac[i]||star[i]) continue;
if(md[i]<=X&&dist[i]<=Y) ans = min(md[i]+dist[i],ans);
}
if(ans<1e12) printf("%lld",ans);
else puts("-1");
}
[풀이] 원래 길이 있는 경우에는 비용이 안드므로 0의 cost, 단방향 통행만 가능하면 그 반대로 오는길은 1의 cost를 준 뒤 다익스트라를 돌려주면 몇개의 단방향 길을 바꿔야 하는지 알 수 있다. 모든 쿼리마다 다익스트라를 돌리면 시간초과가 나므로 출발점 N개에 대해 한번씩만 호출되도록 한다.
(플로이드 워셜로 풀면 훨씬 간결하게 풀 수 있다.)
#include <algorithm>
#include <queue>
#include <cstring>
#include <vector>
#include <cstdio>
using namespace std;
int N,M,K,inf=9e8;
vector<pair<int,int>> adj[251];
int s,e,dist[251][251];
void dijk(){
fill(dist[s],dist[s]+N+1,inf);
priority_queue<pair<int,int>> pq;
dist[s][s]=0;
pq.push({0,s});
while(!pq.empty()){
auto qt = pq.top(); pq.pop();
int cur = qt.second, cd=-qt.first;
if(cd > dist[s][cur]) continue;
for(auto next : adj[cur]){
int nxt = next.first, nd = cd+next.second;
if(dist[s][nxt] > nd){
dist[s][nxt] = nd;
pq.push({-nd,nxt});
}
}
}
}
int main(){
scanf("%d%d",&N,&M);
while(M--){
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
adj[u].push_back({v,0});
adj[v].push_back({u,!k});
}
scanf("%d",&K);
memset(dist,-1,sizeof(dist));
while(K--){
scanf("%d%d",&s,&e);
if(dist[s][s]==-1) dijk();
printf("%d\n",dist[s][e]);
}
}