2017년 4월 7일 금요일

1516 게임개발 (위상정렬)

백준 1516 게임개발 https://www.acmicpc.net/problem/1516

<유사문제 - 위상정렬>
1005 ACM Craft https://www.acmicpc.net/problem/1005 (더 직관적, 더 쉬움)
2623 음악 프로그램 https://www.acmicpc.net/problem/2623(dfs방식, cycle)
2056 작업 https://www.acmicpc.net/problem/2056

주어진 예시를 변경하여 다음과 같은 예를 들어보자.
5
10 -1
10 1 -1
4 1 -1
4 3 1 -1
3 3 2 -1

이 그래프는 사실상 다음과 같은 형태를 가질 것이다. 
(굵은 검정글씨는 정점번호, 흰글씨는 최소 생산시간(갱신안됨))

1. 이 문제에서의 입력을 위의 형태로 구현할 수 있을 까?
할 수는 있겠지만 매우 번거롭고 시간도 많이걸릴것이다. 
(4번정점과같이 연결되는 모든 정점을 표시할때도 있지만 5번정점과같이 인접한 정점만 표시할때도 있기때문에)

2. 그렇다면 입력을 진입차수와 인접리스트로 표현할 수 있을까? 
이것또한 할 수 는 있지만 약간의 의문점이 생길것이다.
정점 4번을 예로들면 직접적으로 연결된 정점은 3번뿐이지만 정점1번도 입력으로 주어졌기 때문에 진입차수와 인접리스트는 2개가 될 것이다. (실질적으론 1개)

이렇게 진입차수가 정확하지 않은데 우리가 구하고자 하는 위상정렬을 할 수 있을까?

정답은 "할 수 있다" 이다. 그 이유는 직접 bfs를 돌리며 알아보도록 하겠다.

int cost[501];            //생산 시간
int indegree[501];        //진입차수
vector<int> vec[501];    //인접리스트
int ans[501];             //최소 생산 시간
cs
저장공간을 위와같이 생성하였다.
먼저 진입차수가 0인지점부터 queue에 삽입해 bfs를 돌린다.
인접차수가 0인지점은 정점 1뿐이니 1에서 갈 수 있는 2, 3, 4를 방문한다. 그 후 2, 3 ,4의 진입차수를 하나씩 빼주고 진입차수가 0이된지점은 다시 queue에 삽입한다.
이때 2, 3, 4의 최소 생산 시간을 갱신하는데 갱신하는 조건은 다음과같다.

$ans[V] = max(ans[V], ans[prev]+cost[V])$
즉, 정점 V번의 최소생산시간은 정점 V번으로 들어오는 값들 중 가장 큰 생산시간 + V번 생산 시간이다. 이것은 정점2, 3이 진입차수가 0이 될때 설명하겠다.

그다음 진입차수가 0인지점인 2, 3에서 bfs를 돌린다.
여기서 정점5번이 갱신되는데 생각해보면 정점5번은 3번이 14초만에 건설된다 할지라도 2번이 완성되야 건설할 수 있기 때문에 이들중 최댓값에서 선택해야되는 것이다. 

이것은 위에 제시했던 의문점또한 해결된다. 
정점 4번을 예시로 들면 1번과 인접하지않지만 인접리스트로 받았음에도 불구하고 이전값보다 더 큰값이 들어오면 갱신되므로 상관이 없어진다.
마지막으로 정점 4, 5에서 bfs를 돌리지만 인접리스트가 없기때문에 결과적으로는 전부 갱신되었다.


#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
int N;
int cost[501];
int ans[501];
int indegree[501];
vector<int> vec[501];
void bfs(){
    queue<int> q;
    for (int n = 1; n <= N; n++){
        if (indegree[n] == 0){
            q.push(n);
            ans[n] = cost[n];
        }
    }
    while (!q.empty()){
        int nedge = q.front();
        q.pop();
        for (int m = 0; m < vec[nedge].size(); m++){
            int e = vec[nedge][m];
            ans[e] = max(ans[e], ans[nedge] + cost[e]);
            if (--indegree[e] == 0)
                q.push(e);
        }
    }
}
int main(){
    scanf("%d"&N);
    for (int n = 1; n <= N; n++){
        int edge;
        scanf("%d"&cost[n]);
        while (scanf("%d"&edge), edge != -1){
            vec[edge].push_back(n);
            indegree[n]++;    //진입차수
        }
    }
    for (int n = 1; n <= N; n++)
        printf("%d\n", indegree[n]);
    bfs();
    for (int n = 1; n <= N; n++)
        printf("%d\n", ans[n]);
    return 0;
}
cs

2805 나무 자르기

백준 2805 나무 자르기 https://www.acmicpc.net/problem/2805

1. Dynamic Programming $O(NlogN + N)$
정렬한 후에
$DP[N] = DP[N-1] + (tree[N-1]-tree[N])*N$의 값이 M이상일 경우에 정지시킨다.
$ans = (DP[N]-M)/N + tree[N]$ 


#include <cstdio>
#include <algorithm>
#include <functional>
using namespace std;
long long N, M;
long long arr[1000002];
long long dp[1000002];
bool cmp(long long a, long long b){return a > b;}
int main(){
    long long ans;
    scanf("%lld%lld"&N, &M);
    for (int n = 0; n < N; n++)
        scanf("%lld"&arr[n]);
    sort(arr, arr + N, cmp);
    dp[0= arr[N] = 0;
    for (long long n = 1; n <= N; n++){
        dp[n] = dp[n - 1+ (arr[n - 1- arr[n]) * n;
        if (dp[n] >= M){
            long long dif = dp[n] - M;
            ans = (long long)(dif / n) + arr[n];
            break;
        }
    }
    printf("%lld\n", ans);
    return 0;
}
cs

2. Binary Search $O(logM)$
M을 이분탐색하며 값을 찾아준다.

#include <cstdio>
long long N, M;
long long arr[1000001];
bool cut(long long h){
    long long sum = 0;
    for (long long n = 0; n < N; n++){
        if (arr[n] > h)
            sum += arr[n] - h;
    }
    return sum >= M;
}
int main(){
    long long ans = 0;
    long long min = 0, max = 10000000000;
    scanf("%lld%lld"&N, &M);
    for (long long n = 0; n < N; n++)
        scanf("%lld"&arr[n]);
    
    while (min <= max){
        long long middle = (min + max) / 2;
        if (cut(middle)){
            if (ans < middle)
                ans = middle;
            min = middle + 1;
        }
        else
            max = middle - 1;
    }
    printf("%lld\n", ans);
    return 0;
}
cs

2017년 4월 6일 목요일

1600 말이 되고픈 원숭이

백준 1600 말이 되고픈 원숭이 https://www.acmicpc.net/problem/1600

<유사문제> 
2206 벽 부수고 이동하기 https://www.acmicpc.net/problem/2206

정답비율 13%, bfs를 사용해서 풀면 금방 해결될거같았는데 간과했던 것이 있던 문제

말처럼 이동할 수 있는 회수가 K번이 존재한다. 말처럼 이동할 수 있는 회수를 변수로 저장하여 0이상이면 bfs를 말의 이동, 원숭이의 이동 돌리고 0이면 원숭이의 이동만 돌린다.
문제는 방문한곳을 저장하는 visit[max][max]배열이다.

K=1, N=4, M=4
0 0 1 1
1 0 1 1
1 0 1 1
1 1 1 0

(1,3)공간까지 원숭이의 이동으로 움직이다 말의 이동으로 움직이면 4번만에 갈 수 있다.

하지만 단순 2차원배열로 저장하면 K가 0이든 1이상이든 가장 빨리 방문한곳은 어찌됬든 방문하지 못하는 최선의 경우만 파악하기 때문에 틀린답이 된다.

∴ visit[K][N][M]으로 K에 따른 방문경우를 따지며 bfs를 돌리면 된다.