최장거리 구하는 문제는 기본적으로 모든 정점을 다 가보고 max값 갱신해주는 형태이다.
백준 10273번 고대동굴탐사 문제처럼 최장거리 뿐 아니라 최대 이득, 최대 비용 이런식으로 비용의 문제로 출제되는 경우도 있다.
한가지 생각할 점은, 모든 정점을 다 가보는 방식이므로 DFS도 효율적이라는 점(BFS를 고집할 필요없다.)
그리고 이문제는 의외로, 방향그래프와 무방향그래프에서의 접근방법이 다른데,
방향그래프에서는 사이클이 없는 DAG이어야 구할 수 있고, 의외로 위상정렬 개념이 들어간다.
위상정렬 개념 없이는 아래와 같은 그래프에서 최장거리 구하기가 까다롭다.
사이클없는 무향그래프(트리)에서 최장거리 구하기
기본적으로 트리이거나 포레스트이므로 단순히 BFS돌리면서 max 갱신만 해주면 된다.
이 문제의 답안이기도 하다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | #include <bits/stdc++.h> using namespace std; using vi = vector<int>; struct node { int v, w; }; using vvn = vector<vector<node>>; //모든 노드로의 longest 한번에 구하기 vi longest(int max_v, int start_v, vvn& adj_list, bool base1 = false) { if (base1) max_v++; vi visit(max_v), max_dist(max_v, -1); queue<node> q; q.push({ start_v,0 }); visit[start_v] = 1; int ln = -1, ld = 0; while (q.size()) { node n = q.front(); q.pop(); if (n.w > max_dist[n.v]) max_dist[n.v] = n.w; for (node adj : adj_list[n.v]) { if (visit[adj.v])continue; visit[adj.v] = 1; q.push({ adj.v, adj.w + n.w }); } } return max_dist; } //최장경로 길이와 해당노드만 리턴 node longest2(int max_v, int start_v, vvn& adj_list, bool base1 = false) { int ln = -1, ld = 0; vi md = longest(max_v, start_v, adj_list, base1); for (int i = (int)base1; i < (int)base1 + max_v; i++) if (i != start_v && md[i] > ld)ld = md[i], ln = i; return { ln,ld }; // longest node, longest distance } int32_t main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); //일단 인접리스트 받자 //V:정점개수 int V; cin >> V; vvn adj_list(V + 1); for (int i = 0; i < V - 1; i++) { int u, v, w; cin >> u >> v >> w; adj_list[u].push_back({ v, w }); adj_list[v].push_back({ u, w }); } node n1 = longest2(V, 1, adj_list, true); node n2 = longest2(V, n1.v, adj_list, true); cout << n2.w << endl; return 0; } | cs |
트리의 지름을 구하는 문제의 답안이기도 한데,
트리의 경우는 임의의 점에서 가장 먼 노드(A)를 찾고 그 노드(A)에서 가장 먼 노드까지의 거리가 트리의 지름입니다. 이 그리디 인사이트만 있으면 풀수있다.
사이클 없는 유향그래프(DAG)에서 최장거리 구하기
이경우 위상정렬을 해야한다(in_edge처리). 이 문제의 답이기도 하다.
또한 이 코드의 경우 모든 경로에 대한 간선개수를 세는 부분도 있으니 참고하자.
보통은 from[N]배열로 단일경로는 찾을 수 있지만, 여기처럼 모든 경로를 찾으려면 max_dist를 이용한 역 탐색이 필요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | #include <bits/stdc++.h> using namespace std; struct node { int v, w; }; using vi = vector<int>; using vvn = vector<vector<node>>; //모든 노드로의 longest 한번에 구하기 vi visited(10001), max_dist(10001, -1); // 간선 카운팅 때문에 밖에 나와있다. vi longest(int max_v, int start_v, vvn& adj_list, vi& in_edge, bool base1 = false) { queue<node> q; function<void(node)> enqueue = [&](node n) { if (in_edge[n.v] != 0) return; q.push(n); }; //start_v와 무관해도 indgree=0인 정점은 모두 넣어야, 위상정렬이 정상 진행됨 for (int i = base1; i < max_v + base1; i++) enqueue({ i,0 }); vi cal(max_v + base1); if (start_v == -1) for (int i = base1; i < max_v + base1; i++) max_dist[i] = 0, cal[i] = 1; else max_dist[start_v] = 0, cal[start_v] = 1; while (q.size()) { auto n = q.front(); q.pop(); for (auto adj : adj_list[n.v]) { if (cal[n.v]) //start_v로 부터의 유효한 정점에 대해서만 max 갱신 max_dist[adj.v] = max(max_dist[adj.v], adj.w + n.w), cal[adj.v]=1; in_edge[adj.v]--; enqueue({ adj.v, max_dist[adj.v] }); } } return max_dist; } int32_t main() { int n, m; cin >> n >> m; vvn adj_list(n + 1), adj_list2(n + 1); vi in(n + 1); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; adj_list[u].push_back({ v, w }); in[v]++; adj_list2[v].push_back({ u, w }); } int s, e; cin >> s >> e; vi md = longest(n, s, adj_list, in, true); cout << md[e] << endl; //이제 거꾸로 가면서 간선 개수 카운팅 set<pair<int, int>> edges; queue<node> q; q.push({ e, md[e] }); vi visited(10001); while (q.size()) { node n = q.front(); q.pop(); if (visited[n.v]) continue; visited[n.v] = 1; if (n.v == s) break; for (node adj : adj_list2[n.v]) { if (n.w - adj.w == max_dist[adj.v]) q.push({ adj.v, n.w - adj.w }), edges.insert({ min(n.v,adj.v), max(n.v,adj.v) }); } } cout << edges.size() << endl; return 0; } | cs |
위의 코드를 한층 더 업그레이드(?)해서.. 간선가중치 뿐 아니라 정점가중치도 고려하고, 경로도 구해주는 버전을 만들었다.
이 문제의 답안이기도 하다. 단, 문제특성상 아래코드는 간선가중치는 작을수록 좋고, 정점코스트는 클수록 좋음에 주의
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include <bits/stdc++.h> using namespace std; const int INF = (int)1e9; struct node { int v, w; }; using vi = vector<int>; using vvn = vector<vector<node>>; //간선가중치(adj_list)뿐 아니라 정점가중치(cost)도 같이 고려. //모든 노드로의 longest 한번에 구하기, 최장거리와 경로도 같이 리턴해준다. struct result { vi max_dist; int max_value; vi path; }; result longest(int max_v,int start_v,vvn& adj_list,vi& in_edge,vi& cost,int base1) { vi visit(max_v + base1), max_dist(max_v + base1, -INF), from(max_v + base1, INF); queue<node> q; function<bool(node)> enqueue = [&](node n)->bool { if (in_edge[n.v] != 0) return false; q.push(n); return true; }; //start_v와 무관해도 indgree=0인 정점은 모두 넣어야, 위상정렬이 정상 진행됨 for (int i = base1; i < max_v + base1; i++) enqueue({ i,cost[i] }); vi cal(max_v + base1); if (start_v == -1) for (int i = base1; i < max_v + base1; i++) max_dist[i] = cost[i], cal[i] = 1; else max_dist[start_v] = cost[start_v], cal[start_v] = 1; while (q.size()) { auto n = q.front(); q.pop(); for (auto adj : adj_list[n.v]) { if (cal[n.v]) //start_v로 부터의 유효한 정점에 대해서만 max 갱신 max_dist[adj.v] = max(max_dist[adj.v], cost[adj.v] - adj.w + n.w), cal[adj.v] = 1; in_edge[adj.v]--; if (enqueue({ adj.v, max_dist[adj.v] })) from[adj.v] = n.v; } } //아래는 좀 비효율적, 위의 복잡도를 높이지 않게 하기위해 걍 놔둠 int ra = -INF, rb = -1; for (int i = base1; i < max_v + base1; i++) if (max_dist[i] > ra) ra = max_dist[i], rb = i; deque<int> s; int ix = rb; while (ix != INF)s.push_front(ix), ix = from[ix]; return { max_dist, ra, vi(s.begin(), s.end()) }; } int32_t main() { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int T; cin >> T; while (T--) { int N, E; cin >> N >> E; vi cost(N + 1); for (int i = 1; i <= N; i++) { cin >> cost[i]; } vector<vector<node>> adj_list(N + 1); vi in(N + 1); for (int i = 0; i < E; i++) { int u, v, w; cin >> u >> v >> w; adj_list[u].push_back({ v, w }); in[v]++; } result r = longest(N, 1, adj_list, in, cost, true); cout << r.max_value << ' ' << (int)(r.path).size() << '\n';; for (auto a : r.path) cout << a << ' '; cout << endl; } return 0; } | cs |
'Programming > Algorithm' 카테고리의 다른 글
최소신장트리(MST, Minimum Spanning Tree) (0) | 2020.04.12 |
---|---|
유니온 파인드(Union-Find), 서로소 집합(Disjoint Set) (0) | 2020.04.10 |
비트연산 (0) | 2020.04.02 |
사이클 검출(Cycle Detection) (0) | 2020.03.30 |
플로이드 와샬 알고리즘(Floyd-Warshall Algorithm) (0) | 2020.03.28 |