최장거리 구하는 문제는 기본적으로 모든 정점을 다 가보고 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) (2) | 2020.03.30 |
| 플로이드 와샬 알고리즘(Floyd-Warshall Algorithm) (0) | 2020.03.28 |
