레이블이 그래프인 게시물을 표시합니다. 모든 게시물 표시
레이블이 그래프인 게시물을 표시합니다. 모든 게시물 표시

2016년 11월 1일 화요일

1939 - 중량제한

https://www.acmicpc.net/problem/1939

S에서 E로 가는 데, 한 번의 이동에서 옮길 수 있는 물품들의 중량의 최대값을 구한다.

문제를 읽고 왠지 이거 다익스트라처럼 그리디하게 풀면 풀리겠다 싶어서 그래프 몇개 끄적거렸는데 무한루프에 빠질것만 같은 공포에 휩싸였다.

dp를 적용하자니 다리의 중량제한 \(c(1 \le c \le 10^9)\) 라서 안되겠다싶었고.

그럼 스패닝 트리를 만들어볼까해서 프림을 생각했는데 몇 개 적어놨던 예제에서부터 안됐다.

그래도 트리로 만드는게 가장 정답같아서 크루스칼로 구현했는데 정답이었다.

크루스칼로 최대 스패닝 트리를 만들고, S에서 E로 가는 경로 중에 가장 중량제한이 작은 것을 출력한다.

#include <bits/stdc++.h>
using namespace std;

#define INF 987654321

typedef pair<int,int> ii;

struct edge {
    int u, v, d;
    bool operator < (const edge& e) const {
        return d < e.d;
    }
};

vector<edge> E;

int group[100001];

int find(int v){
    if(v != group[v])
        return (group[v] = find(group[v]));
    return v;
}

bool Union(int p, int q){
    p = find(p);
    q = find(q);
    if(p == q) return false;
    group[q] = p;
    return true;
}

vector<ii> adj[100001];

bool visit[100001];
int answer(int pos, int dst, int m = INF){
    if(pos == dst) return m;
    
    if(visit[pos]) return 0;
    visit[pos] = true;
    
    for(auto& v : adj[pos]){
        int r = answer(v.first, dst, min(v.second, m));
        if(r != 0) return r;
    }
    return 0;
}

int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    while(m--){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        E.push_back({a-1, b-1, c});
    }
    sort(E.rbegin(), E.rend());
    
    for(int i=0; i<n; ++i) group[i] = i;
    
    for(auto& e : E){
        if( Union(e.u, e.v) ){
            adj[e.u].push_back(ii(e.v, e.d));
            adj[e.v].push_back(ii(e.u, e.d));
        }
    }
    
    int src, dst;
    scanf("%d %d", &src, &dst);
    printf("%d", answer(src-1, dst-1));
    
    return 0;
}

다른 사람들 정답 코드를 보니까 다익스트라도 있었는데, 크루스칼로 푼 사람들 중에 하나를 보고 배웠다.

S에서 E로 가는 경로를 다시 탐색할 필요가 없이, S가 속한 집합과 E가 속한 집합이 같아지는 시점에 연결하는 간선의 중량이 "가능한 가장 큰 중량제한"이었다.

#include <bits/stdc++.h>
using namespace std;
 
#define INF 987654321
 
typedef pair<int,int> ii;
 
struct edge {
    int u, v, d;
    bool operator < (const edge& e) const {
        return d < e.d;
    }
};
 
vector<edge> E;
 
int group[100001];
 
int find(int v){
    if(v != group[v])
        return (group[v] = find(group[v]));
    return v;
}

int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    while(m--){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        E.push_back({a-1, b-1, c});
    }
    sort(E.rbegin(), E.rend());
     
    for(int i=0; i<n; ++i) group[i] = i;
     
    int src, dst;
    scanf("%d %d", &src, &dst);
     
    for(auto& e : E){
        group[find(e.u)] = find(e.v);
        if( find(src-1) == find(dst-1) ){
            printf("%d", e.d);
            break;
        }
    }
     
    return 0;
}

2016년 5월 27일 금요일

알고스팟 - ROUTING

https://algospot.com/judge/problem/read/ROUTING

노이즈의 증폭이 최소가 되는 경로를 찾는 문제이다. 책에는 "노이즈 $c$ 는 언제나 1 이상 3 이하의 실수입니다" 라고 나와있다고 한다.

음.. 이게 딱히 문제는 없을 거 같지만, $c$의 범위를 몰라서 정답의 범위를 모르기때문에, 무한대값(INF)를 설정을 못한다. INF=987654321 로 설정하면 오답이 나온다.
987654321 보다 큰 경로만 남아있을 때, 방문하지 않은 정점들은 987654321 일테니 탐색하지 않아버린다.

근데 모든 노이즈가 3이면 어떻게 될까. 3^10000 ...!!

방문한 노드와 방문하지 않은 노드를 따로 구분해야한다. 다익스트라에서는 음수인 가중치가 없을테니 방문하지 않은 노드를 -1 로 해도 될거같다.

2016년 5월 25일 수요일

1766 - 문제집

https://www.acmicpc.net/problem/1766

처음에는 문제의 접근 방향을 후위순회처럼 생각했다. 어떤 x번째를 하기 위해서 그 이전것을 무조건 해결해야하는 방식.
틀리고 나서 문제를 다시 이해했는데, 깨달은 테스트 케이스부터 말하자면 아래와 같다.
5 3
4 1
2 3
5 3
내가 생각한 이 입력의 정답은
2 4 1 5 3
이다.

1번보다 4번을 먼저 풀어야하고, 3번보다 2, 5번을 먼저 풀어야한다. 1번을 풀기 위해서 4번을 풀어야하는데, 같은 우선순위에서 4번보다는 2번이 쉽다. (둘 다 1개의 문제 앞에 있음)
2번을 풀고 4번을 풀면, 1번이 풀리고 남은 5번을 풀면 3번이 풀린다.

처음에 생각한 대로라면 4 1 2 5 3 이 나와야하는데, 이게 틀려서 위 생각으로 다시 풀었더니 맞았다.
위상정렬 도중에 indegree가 없는 정점을 선택할때마다 번호가 작은(문제 난이도가 낮은) 정점을 선택하게 하면 된다.

2016년 4월 13일 수요일

11581 - 구호물자

https://www.acmicpc.net/problem/11581
2015 인하대학교 프로그래밍 경시대회 G번

처음에는 BFS를 돌면서 다시 방문하는 정점이 있는 경우 NO CYCLE을 출력하고 끝내려했는데, 코딩 미스인건지 오답을 맞았다. (이거 BFS로는 안되는건가요? 누가 댓글로 답변 바람) (참고로 다들 DFS로 푼 것 같다..)

딱히 좋은 아이디어는 안 떠올라서 플로이드 와샬로 정점과 정점이 이동 가능한지 저장했다. 그리고 1번 정점에서 $i$번 정점까지 방문 가능한 것들 중에 $i$번 정점에서 $i$번 정점으로 다시 돌아올 수 있는 (싸이클이 생기는) 경우가 있다면 CYCLE을 출력하도록 했다.

입출력 몇 개를 예로 적어놔야겠다.

예제 #1
6
2 2 3
2 4 6
1 4
1 5
1 3
CYCLE

예제 #2
6
2 2 6
1 6
1 4
2 2 5
1 3
NO CYCLE

2016년 4월 6일 수요일

1613 - 역사

https://www.acmicpc.net/problem/1613

입력으로 주어지는 사건들의 관계를 방향그래프로 생각하면 사건 A와 사건 C 사이에 A->B->C 의 순서가 되는 사건 B가 있으면, 사건 C는 사건 A 이후에 발생했다는 사실을 알 수 있다.

플로이드 와샬을 통해 각 사건들의 관계를 모두 구하면 s줄에 걸친 물음에 대답할 수 있다.

2660 - 회장 뽑기

https://www.acmicpc.net/problem/2660

친구의 친구 사이는 친구이므로, 각 사람들에 대해 서로 가장 가까운 정도를 구할 수 있다.

구해진 가까운 정도(최단 경로)들이 가장 작은 사람들의 리스트를 만들어 출력한다.

2016년 4월 5일 화요일

1507 - 궁금한 민호

https://www.acmicpc.net/problem/1507

(연결된 정점을 x-x 로 표현하겠다)
i-j-k 가 가능한 i-k 가 있다면 i-k 인 간선은 필요가 없다. 왜냐하면 i-k 는 i-j, j-k 로 표현이 가능하고, 같은 거리로 더 많은 도시를 갈 수 있도록 유지되기 때문이다.
결과적으로 필요한 간선만 남게 된다.

불가능한 경우는 잘못된 입력이 주어지는 경우이다. 이를테면 주어진 입력으로 a->b->c 를 해보면 a->c 보다 더 작게 나온다던가 그런 입력.

10217 - KCM Travel

https://www.acmicpc.net/problem/10217

주어진 비용으로 갈 수 있는 1번~모든 노드의 최단 거리를 구한다.

구하는 과정에서 아래와 같이 정보를 저장한다.
dp(i, k) = i번 노드까지 k의 비용으로 갔을 때의 최소 시간
주어진 비용 내에서 N번 노드까지 간 최소 시간을 구하면 된다.

2611 - 자동차 경주

다익스트라에서 큐의 우선순위를 "가중치가 높은 노드"로 바꾸면 된다.

중간에 시작 노드를 지나갈 수 없으므로 (처음을 제외한) 시작 노드 이후의 경로는 탐색하지 않도록 한다.

경로 출력은 큐에 넣는 작업을 할때마다 그 때의 이전 노드를 따로 저장 (즉, 어디서부터 왔는 가를 저장)하여 마지막엔 시작 노드로부터 그 역순으로 따라올라가면 경로를 얻을 수 있다.

2016년 3월 8일 화요일

6156 - Cow Contest

https://www.acmicpc.net/problem/6156

처음에는 위상정렬인가 생각했다. 위상정렬로 어떻게 하지 생각하다가 필요없단 걸 깨달았다.
한 소가 나머지 소들과 한번씩 붙어봤다면 (모두를 알면) 자신의 순위를 알 수 있다.

소를 하나의 정점으로 생각하면 어떤 한 정점과 연결된 선의 갯수가 V-1(자신을 제외한 나머지)개 라면 순위를 알 수 있다.

들어오는 간선(indegree) + 나가는 간선(outdegree) = V-1 (문제에선 N-1) 이면 된다. 정점끼리 서로 연결되었는 지는 방향 그래프를 유지해야하고, 셀 때만 구분없이 세면 된다.

7 6
4 3
3 2
1 2
2 5
5 6
5 7

는 2번 소, 5번 소 = 2개 가 나와야 한다.

2016년 3월 7일 월요일

1389 - 케빈 베이컨의 6단계 법칙

https://www.acmicpc.net/problem/1389

무방향 그래프에서 모든 정점이 서로 얼마만큼 가까운지(최단경로)를 계산하는 문제이다.

모든 정점에 대해 각각 최단경로를 구해도 되지만,(다익스트라라면 $O(V(E+VlogV))$) 문제에서 조건이 2 ≤ N ≤ 100 이므로 플로이드 와샬 알고리즘$O(V^3)$을 사용해도 충분하고 소스가 깔끔하다.

소스 첨부

2016년 3월 3일 목요일

3075 - Astromeeting

미팅하는 사람들이 어떤 은하 k로 가는 비용이 최소가 되는 은하 k를 찾는 문제이다.
그러기 위해선 모든 정점 간의 최단 거리를 구해야하므로, 플로이드 와샬 알고리즘을 썼다.

이제 모든 정점 간의 최단 거리는 구해졌고, (한 정점 K 에서 미팅을 하는 사람의 은하 i)들의 각 비용의 합이 최소가 되는 순간 미팅 장소 은하 K를 갱신했다.

2016년 3월 2일 수요일

1238 - 파티

처음에는 플로이드 와샬로 접근해봤다. 아니나다를까 N=1,000 이라서 $O(N^3)$ 은 시간초과였다.

처음으로 다익스트라를 구현해봤다.
잘 동작하는지는 모르겠고 흉내만 내는 것 같다.
#define INF 987654321

typedef pair<int,int> ii;

int dijkstra(vector<ii> adj[], int n, int s, int e){
    vector<int> cost(n, INF); /* s 로부터 i까지의 최소 비용 */
    priority_queue<ii /*(-weight, next)*/> q;
    q.push({0, s});
    cost[s] = 0;
    while(!q.empty()){
        int pos = q.top().second;
        int dist = -q.top().first;
        q.pop();
        
        for(int i=0; i<adj[pos].size(); ++i){
            ii& next = adj[pos][i]; /* (next, weight) */
            if(dist + next.second >= cost[next.first]) continue;
            q.push({-(dist+next.second), next.first});
            cost[next.first] = dist+next.second;
        }
    }
    return cost[e];
}
예제가 잘 나오길래 제출해봤는데 정답이 잘 나왔다. 시간이 좀 걸리는걸 보니 N이 좀 커지면 시간초과가 나올 듯 하다.

10216 - Count Circle Groups

개인적으로 재미있게 풀었다. 알고스팟 남극기지 문제처럼 좌표평면과 범위가 있어서 엄청 어려워보였다. (주의. 남극기지와는 다른 문제이다!)

시간 제한이 8초인건 나중에 봤고, N이 3,000 이하이길래 충분하다고 생각하고 코딩을 시작했다.

아이디어는 이랬다. (사실 문제에서 말한 그대로 하는거라 아이디어랄 것도 없다.)

아군의 통신 영역이 서로 닿거나 겹친다면 통신 가능하고, 통신이 가능한 부대끼리는 하나의 그룹처럼 이동한다. 여기서 그룹의 개수를 헤아리는 문제인데, 그렇다면 진영을 하나의 노드로 본다면, "몇 개의 그래프가 존재하는가"로 문제를 바꿔 생각할 수 있다.

정점 $A_i$ 에서 $A_j$ 로 통신 가능하다면 이동하도록 인접 리스트를 만든다. ( $O(N^2)$ )
각 정점에서 연결된 모든 정점을 방문하고 개수를 센다. (여기서 하나의 그래프를 발견) . 방문하지 않은 나머지 정점을 확인한다. ( $O(N^2)$ )

그래프를 세는 부분을 코드로 표현하면 아래와 같다.

bool dfs(vector<int> adj[], int pos){
    if(visit[pos]) return false;
    visit[pos] = true;
    for(int i=0; i<adj[pos].size(); ++i)
        dfs(adj, adj[pos][i]);
    return true;
}

memset(visit, false, sizeof(visit));
int count=0;
for(i=0; i<N; ++i){
    count += dfs(adj, i);
}
printf("%d\n", count);

지금 생각해보니 분리집합(Disjoint-set) 으로 했어도 괜찮았을것 같다.

게시글 목록