-
ALOHA 월반 3주차 - 최단 경로 (Dijkstra / Bellman-Ford / Floyd-Warshall)ALOHA - 2022/ALOHA - 초급반 → 중급반 월반 (2022 여름방학) 2022. 8. 9. 02:24
1. [13549] 숨바꼭질 3 (Gold V)
더보기의도한 태그: Dijkstra (Easy)
정점은 그냥 좌표고, 간선은 문제에서 충분히 자세히 알려주고 있습니다.
탐색 범위를 넉넉하게 잡는 걸 추천합니다. 저는 자세한 생각하기 귀찮아서 목적지에 2를 곱한
으로 잡았습니다.const ll INF = 0x3f3f3f3f3f3f3f3f; ll dis[200020]; ll djk(int st, int ed){ memset(dis, 0x3f, sizeof(dis)); dis[st] = 0; priority_stack<pl2> pq; pq.push({0, st}); while (!pq.empty()){ int now = pq.top().sc; ll dst = pq.top().fr; pq.pop(); if (dst != dis[now]){ continue; } for (pl2 p : { mkp(now*2, 0), mkp(now+1, 01), mkp(now-1, 1) }){ int nxt = p.fr; int d = p.sc; if (0 > nxt || nxt > 200000){ continue; } if (dis[nxt] <= dst+d){ continue; } dis[nxt] = dst+d; pq.push({dst+d, nxt}); } } return dis[ed]; } void Main(){ int st, ed; cin >> st >> ed; cout << djk(st, ed); }
더보기사실 Well-known? 0-1 BFS로 더 맛있게 풀 수 있습니다.
코드는 귀찮으니까 나중에 짜야지
2. [2458] 키 순서 (Gold IV)
더보기의도한 태그: Floyd-Warshall (Easy)
두 학생
의 키를 비교할 수 있다면, 에서 로의 (또는 그 반대 방향의) 경로가 존재해야 합니다.그리고, 어떤 학생의 순위를 특정하기 위해서는, 다른 모든 학생들과 키를 비교할 수 있어야 합니다.
그러니, 플로이드를 돌려서 모든
의 경로 존재 여부를 판별한 뒤,다른 모든 학생들과의 경로가 존재하는
의 개수를 세주면 됩니다.bool adj[520][520]; void Main(){ int n, m; cin >> n >> m; for (int i = 1; i <= n; i++){ adj[i][i] = 1; } while (m--){ int v, w; cin >> v >> w; adj[v][w] = 1; } for (int k = 1; k <= n; k++){ for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ adj[i][j] = adj[i][j] || (adj[i][k] && adj[k][j]); } } } int ans = 0; for (int i = 1; i <= n; i++){ bool res = 1; for (int j = 1; j <= n; j++){ res = res && (adj[i][j] || adj[j][i]); } ans += res; } cout << ans; }
3. [11657] 타임머신 (Gold IV)
더보기의도한 태그: Bellman-Ford (Easy)
벨만 포드 구현 문제입니다.
저거 말고는 딱히 할 말이 없으니 자주 틀리는 부분 하나 놓고 가겠습니다.
음수 사이클의 판별에서 '1번 정점에서 닿을 수 없은 음수 사이클'을 잘못 판별하고 있는 건 아닌지 주의해주세요.
vector<pi3> edg; const ll INF = 1e15; ll dis[520]; void Main(){ int n, m; cin >> n >> m; for (int i = 2; i <= n; i++){ dis[i] = INF; } while (m--){ int v, w, x; cin >> v >> w >> x; edg.push_back({ {v, w}, x }); } for (int t = 1; t <= n; t++){ for (pi3 p : edg){ int v = p.fr.fr, w = p.fr.sc, x = p.sc; if (dis[v] == INF){ continue; } if (dis[w] <= dis[v]+x){ continue; } dis[w] = dis[v] + x; if (t == n){ cout << -1; return; } } } for (int i = 2; i <= n; i++){ cout << (dis[i] == INF ? -1 : dis[i]) << endl; } }
4. [10159] 저울 (Gold III)
더보기의도한 태그: Floyd-Warshall (Normal)
이 문제의 약화 버전인 2번 문제의 풀이를 먼저 읽고 오자.
이 문제의 풀이는 거기에 한 문장만 더 추가하면 된다.
각 물건별로, 무게를 비교할 수 없는 물건의 개수는 '그 물건까지의 경로가 존재하지 않는' 물건의 개수이다.
그러니 저걸 대신 세서 출력하면 된다.
bool adj[520][520]; void Main(){ int n, m; cin >> n >> m; for (int i = 1; i <= n; i++){ adj[i][i] = 1; } while (m--){ int v, w; cin >> v >> w; adj[v][w] = 1; } for (int k = 1; k <= n; k++){ for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ adj[i][j] = adj[i][j] || (adj[i][k] && adj[k][j]); } } } for (int i = 1; i <= n; i++){ int ans = 0; for (int j = 1; j <= n; j++){ ans += !adj[i][j] && !adj[j][i]; } cout << ans << endl; } }
5. [1865] 웜홀 (Gold III)
더보기의도한 태그: Bellman-Ford (Normal)
하지만 Floyd-Warshall로 푼다면 어떨까?
답은 굉장히 간단한데, 그냥
에서 로 가는 최단경로의 길이가 미만인지 판별하면 된다.굉장히 정직하면서도 AC를 받아주는 착한 풀이다.
ll adj[520][520]; int main(){ Fast; int t; cin >> t; while (t--){ int n, m, w; cin >> n >> m >> w; for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) adj[i][j] = (i==j ? 0 : INF); while (m--){ ll x, y, z; cin >> x >> y >> z; adj[x][y] = min(adj[x][y], z); adj[y][x] = min(adj[y][x], z); } while (w--){ ll x, y, z; cin >> x >> y >> z; adj[x][y] = min(adj[x][y], -z); } for (int k = 1; k <= n; k++){ for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ adj[i][j] = min(adj[i][j], adj[i][k] + adj[k][j]); } } } bool neg = false; for (int i = 1; i <= n; i++) neg = (neg || adj[i][i] < 0); cout << (neg ? "YES" : "NO") << endl; } }
6. [9370] 미확인 도착지 (Gold II)
더보기의도한 태그: Dijkstra (Hard)
예술가들은, 중간에
의 간선을 타고 지나갑니다.그러니, 예술가들의 경로는
또는 가 됩니다.그러니, 각 후보지점
에 대해서 위 경로의 길이와 의 길이가 동일한지 보면 됩니다.const ll INF = 1e15; int n, m, k; vector<pl2> adj[2020]; ll dis[3][2020]; void f(int st, int idx){ for (int i = 1; i <= n; i++){ dis[idx][i] = INF; } priority_queue< pl2, vector<pl2>, greater<pl2> > pq; pq.push({0, st}); dis[idx][st] = 0; while (!pq.empty()){ ll dst = pq.top().fr; int now = pq.top().sc; pq.pop(); if (dst != dis[idx][now]){ continue; } for (pl2 p : adj[now]){ int nxt = p.fr; ll d = p.sc; if (dis[idx][nxt] <= d+dst){ continue; } dis[idx][nxt] = d+dst; pq.push({dis[idx][nxt], nxt}); } } } void Main(){ int t; cin >> t; while (t--){ cin >> n >> m >> k; int st, x, y; cin >> st >> x >> y; ll d = 0; while (m--){ int a, b, c; cin >> a >> b >> c; if (a==x && b==y || a==y && b==x){ d = c; } adj[a].push_back({b, c}); adj[b].push_back({a, c}); } f(st, 0); f(x, 1); f(y, 2); vector<int> ans; while (k--){ int a; cin >> a; if (dis[0][a] == dis[0][x] + d + dis[2][a] || dis[0][a] == dis[0][y] + d + dis[1][a]){ ans.push_back(a); } } sort( all(ans) ); for (int x : ans){ cout << x << ' '; } cout << endl; for (int i = 1; i <= n; i++){ adj[i].clear(); } } }
7. [2176] 합리적인 이동경로 (Gold I)
더보기의도한 태그: Dijkstra (Normal)
에 가까워지면서 이동한다는 건, 와의 거리가 점점 줄어든다는 의미입니다.그러니, 모든 정점에서
까지의 거리를 미리 찾은 뒤, 이를 토대로 아래 DP를 돌려주면 됩니다. 에서 출발해서 까지 가는 합리적인 이동경로의 개수const ll INF = 0x3f3f3f3f3f3f3f3f; vector<pl2> adj[1020]; ll dis[1020]; bool djk(int st){ memset(dis, 0x3f, sizeof(dis)); dis[st] = 0; priority_stack<pl2> pq; pq.push({0, st}); while (!pq.empty()){ int now = pq.top().sc; ll dst = pq.top().fr; pq.pop(); if (dst != dis[now]){ continue; } for (pl2 p : adj[now]){ int nxt = p.fr; int d = p.sc; if (dis[nxt] <= dst+d){ continue; } dis[nxt] = dst+d; pq.push({dst+d, nxt}); } } return dis[1] != INF; } ll dp[1020]; ll dpf(int now){ if (dp[now] != -1){ return dp[now]; } if (now == 2){ return dp[now] = 1; } dp[now] = 0; for (pl2 p : adj[now]){ int nxt = p.fr; if (dis[now] <= dis[nxt]){ continue; } dp[now] += dpf(nxt); } return dp[now]; } void Main(){ int n, m; cin >> n >> m; while (m--){ int v, w, x; cin >> v >> w >> x; adj[v].push_back({w, x}); adj[w].push_back({v, x}); } if( !djk(2) ){ cout << 0; return; } memset(dp, -1, sizeof(dp)); cout << dpf(1); }
8. [19537] 전투 시뮬레이션 (Platinum V)
더보기의도한 태그: Dijkstra (Hard)
사실 하라는 건 문제에 다 나와있습니다.
문제가 길고 구현할 게 많아서 그렇지...
굳이 풀이로 한 마디를 붙이자면... 이동 가능 여부를 '지날 수 있는 칸만 사용하는 최단경로'로 판별해주면 됩니다.typedef pair<ll, pll> plll; struct unit{ int hp, team, y, x; }; int mp[520][520]; int tck[2][520][520]; int d[10]; unit arr[250020]; int n, h, w; inline bool f(int idx, int ey, int ex){ //cout << "F " << idx << ' ' << ey << ' ' << ex << endl; priority_queue<plll, vector<plll>, greater<plll> > pq; bool chk[520][520] = {}; pq.push({0, {arr[idx].y, arr[idx].x}}); while (!pq.empty()){ int dis = pq.top().fr, y = pq.top().sc.fr, x = pq.top().sc.sc; pq.pop(); //cout << "PQ " << dis << ' ' << y << ' ' << x << endl; if (y == ey && x == ex) return true; if (chk[y][x]) continue; chk[y][x] = true; for (int k = 0; k < 4; k++){ int yy = y+dy[k], xx = x+dx[k]; //cout << endl; //cout << yy << ' ' << xx << ' '; if (1 > yy || yy > h || 1 > xx || xx > w) continue; //cout << "COND1 "; if (chk[yy][xx] || mp[yy][xx] == -1) continue; //cout << "COND2 " << mp[yy][xx] << ' '; if (dis+mp[yy][xx] > arr[idx].hp) continue; //cout << "COND3 "; if (tck[ arr[idx].team ^ 1 ][yy][xx] != 0 && (yy!=ey || xx!=ex)) continue; //cout << "COND4 "; //cout << "PUSH " << dis+mp[yy][xx] << ' ' << yy << ' ' << xx << endl; pq.push({dis+mp[yy][xx], {yy, xx}}); } } return false; } inline void g(int team, int y, int x, int val){ tck[team][y][x] += val; for (int k = 0; k < 4; k++){ int yy = y+dy[k], xx = x+dx[k]; if (1 > yy || yy > h || 1 > xx || xx > w) continue; tck[team][yy][xx] += val; } } void Main(){ cin >> n >> h >> w; for (int i = 1; i <= h; i++){ for (int j = 1; j <= w; j++){ cin >> mp[i][j]; } } for (int i = 1; i <= n; i++){ cin >> d[i]; } for (int i = 1; i <= h; i++){ for (int j = 1; j <= w; j++){ mp[i][j] = d[ mp[i][j] ]; } } int m; cin >> m; for (int i = 1; i <= m; i++){ cin >> arr[i].hp >> arr[i].team >> arr[i].y >> arr[i].x; arr[i].y += 1; arr[i].x += 1; g(arr[i].team, arr[i].y, arr[i].x, 1); } int q; cin >> q; while (q--){ int u, y, x; cin >> u >> y >> x; y += 1; x += 1; bool chk = false; for (int i = 1; i <= m; i++){ chk |= arr[i].y == y && arr[i].x == x; } if (chk) continue; if (f(u, y, x)){ g(arr[u].team, arr[u].y, arr[u].x, -1); arr[u].y = y; arr[u].x = x; g(arr[u].team, arr[u].y, arr[u].x, 1); } /* for (int i = 1; i <= h; i++){ for (int j = 1; j <= w; j++){ cout << tck[0][i][j] << ' '; } cout << endl; } cout << endl; for (int i = 1; i <= h; i++){ for (int j = 1; j <= w; j++){ cout << tck[1][i][j] << ' '; } cout << endl; } cout << endl; */ /* cout << "COOR" << endl; for (int i = 1; i <= m; i++){ cout << arr[i].y - 1 << ' ' << arr[i].x - 1 << endl; } */ } for (int i = 1; i <= m; i++){ cout << arr[i].y - 1 << ' ' << arr[i].x - 1 << endl; } }
추가 한 마디: 이거 누가 셋에 넣었음 :blobangry: (본인이 넣었음)
원래 코드 읽어보면서 마음에 안 드는 코드는 다시 짜서 넣는데
이건 마음에 안 들지만 다시 짜기도 싫어요 ㅋㅋ
9. [1854] K번째 최단경로 찾기 (Platinum IV)
더보기의도한 태그: Dijkstra (Hard)
각 정점별로,
개의 최단 경로를 저장해가면서 다익스트라를 돌리면 됩니다.네. 정말 저거만 하면 됩니다.
경로의 업데이트는 지금 보고 있는 경로가
번째 안으로 들어가는지만 확인하면 됩니다.그러니 경로의 길이를 저장하는 최대 힙을 하나 만들고, 그 안의 원소를
개로 유지시키면서 비교해주면 됩니다.const ll INF = 0x3f3f3f3f3f3f3f3f; vector<pl2> adj[10020]; priority_queue<ll> dis[10020]; int cnt[10020]; void djk(int st, int k){ priority_queue< pl2, vector<pl2>, greater<pl2> > pq; pq.push({0, 1}); dis[st].push(0); //cout << "HUH" << endl << flush; while (!pq.empty()){ int now = pq.top().sc; ll dst = pq.top().fr; //cout << "PQ " << now << ' ' << cnt[now] << " = " << dst << endl << flush; pq.pop(); cnt[now] += 1; if (cnt[now] > k){ continue; } for (pl2 p : adj[now]){ int nxt = p.fr; ll d = p.sc; if (dis[nxt].size() == k && dis[nxt].top() <= dst+d){ continue; } pq.push({dst+d, nxt}); dis[nxt].push(dst+d); if (dis[nxt].size() > k){ dis[nxt].pop(); } } } } void Main(){ int n, m, k; cin >> n >> m >> k; while (m--){ int v, w, x; cin >> v >> w >> x; adj[v].push_back({w, x}); } djk(1, k); for (int i = 1; i <= n; i++){ if (dis[i].size() < k){ cout << -1; } else{ cout << dis[i].top(); } cout << endl; } }
10. [23258] 밤편지 (Gold I)
더보기의도한 태그: Floyd-Warshall (Additional)
플로이드 와샬에 대한 깊은 이해로 풀 수 있습니다.
플로이드 와샬에 대한 명확한 정의는 아래와 같은 DP입니다.
에서 로 가는 최단 경로. 단, 중간에 타는 정점의 번호는 보다 작거나 같아야 한다.그리고 이 DP의 초항과 점화식은 아래와 같습니다.
(중간에 지나는 정점이 없어야 하므로, 그냥 그래프 그대로 들고 오면 된다.) (각각 번 정점을 지날 때와 지나지 않을 때이다.)그럼 이걸로 문제를 어떻게 풀까요?
이슬을
방울 이상 마실 수 없다는 건 번호가 이상인 정점을 지날 수 없다와 동치입니다.(
이기 때문입니다. 이진수로 나타내면 이해가 쉬울 거에요.)그러니,
로 들어오는 쿼리는 로 처리해줄 수 있습니다.그러니 플로이드 와샬의 중간과정을 다 저장하면서 돌려주면 문제를 풀 수 있습니다.
const ll INF = 1e15; ll dis[302][302][302]; void Main(){ int n, q; cin >> n >> q; for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ cin >> dis[0][i][j]; if (dis[0][i][j] == 0 && i != j){ dis[0][i][j] = INF; } } } for (int k = 1; k <= n; k++){ for (int i = 1; i <= n; i++){ for (int j = 1; j <= n; j++){ dis[k][i][j] = min(dis[k-1][i][j], dis[k-1][i][k] + dis[k-1][k][j]); } } } while (q --> 0){ int st, ed, k; cin >> k >> st >> ed; if (dis[k-1][st][ed] == INF){ cout << -1 << endl; } else{ cout << dis[k-1][st][ed] << endl; } } }
'ALOHA - 2022 > ALOHA - 초급반 → 중급반 월반 (2022 여름방학)' 카테고리의 다른 글
ALOHA 월반 5주차 - Union Find / Minimum Spanning Tree (0) 2022.08.30 ALOHA 월반 4주차 - DP (Interval DP / LIS / LCS) (0) 2022.08.19 ALOHA 월반 2주차 - 그래프 (DFS / BFS / Backtracking) 풀이 (0) 2022.08.01 ALOHA 월반 1주차 - 완전탐색 / 그리디 / 분할정복 (0) 2022.07.27