페이지

레이블이 floyd-warshall algorithm인 게시물을 표시합니다. 모든 게시물 표시
레이블이 floyd-warshall algorithm인 게시물을 표시합니다. 모든 게시물 표시

7332번: Cashier Employment

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

각 시간대마다 필요한 알바 수와 8시간동안 연속적으로 고용할 수 있는 알바 정보가 주어졌을 때 필요한 알바 수의 최솟값을 구하는 문제이다.

r[i]: i-1 ~ i시에 필요한 알바 수
t[i]: i-1 ~ i+7시에 쓸 수 있는 알바 수
* 굳이 i-1로 한 이유는 후에 prefix sum을 사용하는데 있어 -1번째 인덱스가 나오지 않게 하기 위함이다.

그러면 문제는 0<= a[i] <= t[i]인 a[i]를 잘 골라
i<8 일 때, a[17+i] + ... + a[24] + a[1] + ... + a[i] >= r[i]
i>=8 일 때, a[i-7] + a[i-6] + ... + a[i] >= r[i]
를 만족하는 sum(a[i])의 최솟값을 구하는 문제라고 할 수 있다.

s[i] = sum(a[j]: 1<=j<=i)로 놓고 모든 조건을 일차선형부등식 꼴로 만들어 보면
0 <= s[i] - s[i-1] <= t[i]
i<8 일 때, s[24] - s[16+i] + s[i] >= r[i]
i>=8 일 때, s[i] - s[i-8] >= r[i]

이제 상수 lmt <= s[24]로 놓으면
s[24] - lmt >= s[0]
s[i] >= s[i-1]
s[i-1] + t[i] >= s[i]
i<8 일 때, s[i] + lmt - r[i] >= s[16+i]
i>=8 일 때, s[i] - r[i] >= s[i-8]

모든 부등식이 x + w >= y 꼴의 형태이므로 각 조건에 대응되는 가중치가 w인 x -> y 간선들을 추가한 뒤, 최단거리 알고리즘을 이용해 주어진 부등식들이 가능한지 판단할 수 있다.
부등식들이 모두 성립하는 최소 0 <= lmt <= n를 찾는다. 불가능하면 No Solution을 출력한다.

시간복잡도는 테스트 케이스마다 $O(n)$

#include<cstdio>
#include<algorithm>
using namespace std;
int tc, n, t[25], r[25], dp[25][25];
bool f(int lmt) {
    fill(dp[0], dp[25], 1e9);
    for (int i = 0; i < 25; i++) {
        dp[i][i] = 0;
        if (i) dp[i - 1][i] = t[i], dp[i][i - 1] = 0;
    }
    int i = 1;
    for (; i < 8; i++) dp[i][i + 16] = lmt - r[i];
    for (; i < 25; i++) dp[i][i - 8] = -r[i];
    dp[24][0] = -lmt;
    for (int i = 0; i < 25; i++) for (int j = 0; j < 25; j++) for (int k = 0; k < 25; k++) dp[j][k] = min(dp[j][k], dp[j][i] + dp[i][k]);
    for (int i = 0; i < 25; i++) if (dp[i][i] < 0) return false;
    return true;
}
void task() {
    for (int i = 1; i <= 24; i++) scanf("%d", r + i), t[i] = 0;
    scanf("%d", &n);
    for (int i = 0, x; i < n; i++) scanf("%d", &x), t[x + 1]++;
    for (int i = 0; i <= n; i++) if (f(i)) { printf("%d\n", i); return; }
    puts("No Solution");
}
int main() {
    for (scanf("%d", &tc); tc--;) task();
    return 0;
}

13168번: 내일로 여행

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

내일로 티켓을 사용했을 때와 사용하지 않았을 때의 도시간 이동하는데 드는 최소 비용을 플로이드 알고리즘으로 구한다. 두 계획에 대해 각각 여행에 드는 비용을 계산하고 비교한다.

시간복잡도는 $O(k\lg n +n^3)$

#include<iostream>
#include<string>
#include<map>
#include<algorithm>
using namespace std;
int n, m, r, k, cost[10000], dp[100][100], c;
string plan[200], t, type[10000], s[10000], e[10000];
map<string, int> city;
int main() {
    cin >> n >> r; r <<= 1;
    fill(dp[0], dp[n], 1e9);
    for (int i = 0; i < n; i++) {
        cin >> t;
        city[t] = i;
        dp[i][i] = 0;
    }
    cin >> m;
    for (int i = 0; i < m; i++) cin >> plan[i];
    cin >> k;
    for (int i = 0; i < k; i++) {
        cin >> type[i] >> s[i] >> e[i] >> cost[i]; cost[i] <<= 1;
        dp[city[e[i]]][city[s[i]]] = dp[city[s[i]]][city[e[i]]] = min(dp[city[s[i]]][city[e[i]]], cost[i]);
    }
    for (int x = 0; x < n; x++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) dp[i][j] = min(dp[i][j], dp[i][x] + dp[x][j]);
    for (int i = 1; i < m; i++) c += dp[city[plan[i - 1]]][city[plan[i]]];
    for (int i = 0; i < k; i++) {
        if (type[i][1] == '-') cost[i] /= 2;
        if (type[i].size() > 8) cost[i] = 0;
        dp[city[e[i]]][city[s[i]]] = dp[city[s[i]]][city[e[i]]] = min(dp[city[s[i]]][city[e[i]]], cost[i]);
    }
    for (int x = 0; x < n; x++) for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) dp[i][j] = min(dp[i][j], dp[i][x] + dp[x][j]);
    for (int i = 1; i < m; i++) r += dp[city[plan[i - 1]]][city[plan[i]]];
    puts(c > r ? "Yes" : "No");
    return 0;
}

2795번: KAMPANJA

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

LA에 가는길과 오는길 모두 포함되어 있는 정점들을 가는길에서 방문한 순서대로 정렬한 수열을 s_1, 오는길에서 방문한 순서대로 정렬한 수열을 s_2라 하자. 또한, s_k 내에서 x는 idx_k(x)번째에 방문했다고 하자.

임의의 s, e에 대해 idx_1(s) <= idx_1(e) & idx_2(s) <= idx_2(e)를 만족하면 s<=s'<=e'<=e인 임의의 s', e'에 대해 idx_1(s')<=idx_1(e') & idx_2(s') <= idx_2(e')을 만족한다.
그렇지 않으면 가는길과 오는길의 s에서 e로 가는 최단경로가 다르므로 모순이다.

이 성질을 만족하는 경로 모양은 꽤나 단순해진다. 오는길과 가는길을 잘 정리해서 그려보면 DNA 같은 모양을 하고 있다.

이 그래프상에서 최단 거리를 구하기 위해 다음과 같은 배열 b를 정의한다.
b[i][j]: 1 ->...-> i ->...-> 2 ->...-> j ->...-> 1가 되기 위해 고용해야 하는 경호원 수
그러면 b[i][j] = min(b[i][k]+dist(j,k) - (i==j), b[k][j]+dist(i,k) - (i==j), b[j][i] + dist(j,i) - 1) (단, dist(i,i) = inf)를 만족한다.

모든 dist(i,j)쌍을 알고있다면 b[i][j]는 최단경로 알고리즘으로 구할 수 있다. 아래 소스에서는 dist(i,j)를 플로이드 알고리즘으로, b[i][j]는 벨만포드 알고리즘으로 구했다.

시간복잡도는 $O(n^2m)$

#include<cstdio>
#include<algorithm>
using namespace std;
int n, m, a[101][101], b[101][101], x[200], y[200];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) a[i][j] = b[i][j] = 1e9;
    for (int i = 0; i < m; i++) scanf("%d%d", x + i, y + i), a[x[i]][y[i]] = 1;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) a[j][k] = min(a[j][k], a[j][i] + a[i][k]);
    b[1][1] = 1;
    for (int i = n; i--;) {
        for (int j = 1; j <= n; j++) {
            for (int k = 0; k < m; k++) {
                b[y[k]][j] = min(b[y[k]][j], b[x[k]][j] + (j != y[k]));
                b[j][x[k]] = min(b[j][x[k]], b[j][y[k]] + (j != x[k]));
            }
            for (int k = 1; k <= n; k++) b[j][k] = min(b[j][k], b[k][j] + a[k][j] - 1);
        }
    }
    printf("%d", b[2][2]);
    return 0;
}

2610번: 회의준비

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


$O(n^3)$

모든 서로 아는 사이에 단위 가중치를 두고 플로이드 알고리즘을 쓰면 임의의 두 사람 사이 의사전달시간을 알 수 있다.

#include<cstdio>
#include<algorithm>
using namespace std;
int n, m, d[101][101], maxi[101], res[100], sz, flag[101];
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i^j) d[i][j] = 1e9;
    for (int i = 0, x, y; i < m; i++) {
        scanf("%d%d", &x, &y);
        d[x][y] = d[y][x] = 1;
    }
    for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
        if (d[i][k] + d[k][j] < d[i][j]) d[i][j] = d[i][k] + d[k][j];
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (d[i][j]<1e9&&d[i][j]>maxi[i]) maxi[i] = d[i][j];
    for (int i = 1; i <= n; i++) if (!flag[i]) {
        int t = i;
        for (int j = i; j <= n; j++) if (d[i][j]<1e9) {
            flag[j] = 1;
            if (maxi[t] > maxi[j]) t = j;
        }
        res[sz++] = t;
    }
    sort(res, res + sz);
    printf("%d", sz);
    for (int i = 0; i < sz; i++) printf("\n%d", res[i]);
    return 0;
}

2660번: 회장뽑기

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


$O(n^3)$

친구 사이이면 가중치를 1, 그렇지 않으면 무한대로 설정하고 플로이드 알고리즘을 돌린다.
회원 x의 점수는 x와 나머지 회원간 최대 가중치이다.

#include<cstdio>
int d[51][51], n, x, y, maxi[51], can = 1e9, cnt;
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) if (i^j) d[i][j] = 1e9;
    while (scanf("%d%d", &x, &y), ~x) d[x][y] = d[y][x] = 1;
    for (int k = 1; k <= n; k++) for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++)
        if (d[i][j] > d[i][k] + d[k][j]) d[i][j] = d[i][k] + d[k][j];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) if (maxi[i] < d[i][j]) maxi[i] = d[i][j];
        if (can > maxi[i]) can = maxi[i], cnt = 0;
        cnt += can == maxi[i];
    }
    printf("%d %d\n", can, cnt);
    for (int i = 1; i <= n; i++) if (maxi[i] == can) printf("%d ", i);
    return 0;
}

1956번: 운동

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


$O(v^3)$

초기에 i->i 가는 비용을 무한대로 설정하고 플로이드 알고리즘을 돌려본다.
이후 i->i 가는 비용의 최솟값을 출력한다. 이 값이 무한대이면 사이클은 존재하지 않는다.

#include<cstdio>
int v, e, dis[401][401], mini = 1e9, x, y, z;
int main() {
    scanf("%d%d", &v, &e);
    for (int i = 1; i <= v; i++)
        for (int j = 1; j <= v; j++) dis[i][j] = 1e9;
    while (e--) scanf("%d%d%d", &x, &y, &z), dis[x][y] = z;
    for (int k = 1; k <= v; k++) for (int i = 1; i <= v; i++) for (int j = 1; j <= v; j++)
        if (dis[i][j] > dis[i][k] + dis[k][j]) dis[i][j] = dis[i][k] + dis[k][j];
    for (int i = 1; i <= v; i++) if (mini > dis[i][i]) mini = dis[i][i];
    printf("%d", mini < 1e9 ? mini : -1);
    return 0;
}

10159번: 저울

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


#include<stdio.h>
int N, M, data[101][101], cnt;
int main()
{
    int i, j, k, x, y;
    scanf("%d", &N);
    scanf("%d", &M);
    for (i = 1; i <= M; i++)
    {
        scanf("%d %d", &x, &y);
        data[x][y] = 1;
    }
    for (i = 1; i <= N; i++)
    {
        for (j = 1; j <= N; j++)
        {
            for (k = 1; k <= N; k++)
            {
                if (data[j][i] && data[i][k])
                    data[j][k] = 1;
            }
        }
    }
    for (i = 1; i <= N; i++)
    {
        cnt = 0;
        for (j = 1; j <= N; j++)
        {
            if (!data[i][j] && !data[j][i])
                cnt++;
        }
        printf("%d\n", cnt - 1);
    }
    return 0;
}

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

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


$O(m+n^3)$


#include<cstdio>
int n, m, c[101][101], r, mini = 1e9;
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) c[i][j] = (i != j)*1e9;
    for (int i = 0, x, y; i< m; i++)scanf("%d %d", &x, &y), c[x][y] = c[y][x] = 1;
    for (int i = 1; i <= n; i++)for (int j = 1; j <= n; j++)for (int k = 1; k <= n; k++) if (c[j][i] + c[i][k] < c[j][k])c[j][k] = c[j][i] + c[i][k];
    for (int i = 1; i <= n; i++) {
        int s = 0;
        for (int j = 1; j <= n; j++) s += c[i][j];
        if (s < mini)mini = s, r = i;
    }
    printf("%d", r);
    return 0;
}

1613번: 역사

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


$O(n^3)$

x가 y보다 먼저 일어났다면 x->y 간선을 생성한다.
x와 y사이 x->t->y와 같이 t를 경유할 수 있다면 x->y를 만든다.
이는 플로이드 알고리즘으로 $O(n^3)$ 시간에 가능하다.


#include<cstdio>
int n, k, s, c[401][401], x, y;
int main() {
    scanf("%d%d", &n, &k);
    while (k--) scanf("%d%d", &x, &y), c[x][y] = 1;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++) c[j][k] |= c[j][i] & c[i][k];
    scanf("%d", &s);
    while (s--) scanf("%d%d", &x, &y), printf("%d\n", c[y][x] - c[x][y]);
    return 0;
}

11404번: 플로이드

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


$O(n^3)$


#include<cstdio>
int n, m, c[101][101];
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (i - j)c[i][j] = 1e9;
    for (int i = 0, x, y, z; i < m; i++) scanf("%d %d %d", &x, &y, &z), c[x][y] = c[x][y]<z ? c[x][y] : z;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) for (int k = 1; k <= n; k++)
        if (c[j][i] + c[i][k] < c[j][k]) c[j][k] = c[j][i] + c[i][k];
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) printf("%d ", c[i][j] == 1e9 ? 0 : c[i][j]);
        puts("");
    }
    return 0;
}

11403번: 경로 찾기

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


$O(n^3)$

플로이드 알고리즘을 사용한다.


#include<cstdio>
int n, c[100][100];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &c[i][j]);
    for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) for (int k = 0; k < n; k++) c[j][k] |= c[j][i] & c[i][k];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) printf("%d ", c[i][j]);
        puts("");
    }
    return 0;
}

7577번: 탐사

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


#include<stdio.h>
int l, n, w[41][41];
int main() {
    scanf("%d %d", &l, &n);
    for (int i = 0; i <= l; i++)
        for (int j = 0; j <= l; j++) if (i != j) w[i][j] = 1e9;
    for (int i = 0; i < l; i++) w[i + 1][i] = 0, w[i][i + 1] = 1;
    for (int i = 0, x, y, z; i < n; i++) {
        scanf("%d %d %d", &x, &y, &z);
        if (w[x - 1][y]>z) w[x - 1][y] = z;
        w[y][x - 1] = -z;
    }
    for (int i = 0; i <= l; i++)
        for (int j = 0; j <= l; j++)
            for (int k = 0; k <= l; k++)
                if (w[j][i] + w[i][k]<w[j][k]) w[j][k] = w[j][i] + w[i][k];
    for (int i = 0; i <= l; i++) if (w[i][i] < 0) {
        puts("NONE");
        return 0;
    }
    for (int i = 0; i < l; i++) putchar(w[0][i + 1] - w[0][i] ? '#' : '-');
    return 0;
}

1504번: 특정한 최단 경로

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


$O(n^3)$

n범위가 작으므로 플로이드 알고리즘으로 최단 거리를 구해보자.
경로가 존재한다면 1-p-q-n, 1-q-p-n 중 최단 경로 비용을 출력한다.


#include<stdio.h>
#include<algorithm>
using namespace std;
const int MAX_N = 8e2;
int w[MAX_N + 1][MAX_N + 1], n, e, p, q, res;
int main() {
    scanf("%d %d", &n, &e);
    fill(&w[0][0], &w[MAX_N][MAX_N + 1], 1e8);
    for (int i = 0, x, y, z; i < e; i++) {
        scanf("%d %d %d", &x, &y, &z);
        w[x][y] = w[y][x] = z;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            for (int k = 1; k <= n; k++)
                if (w[j][i] + w[i][k] < w[j][k]) w[j][k] = w[j][i] + w[i][k];
    scanf("%d %d", &p, &q);
    w[p][p] = w[q][q] = 0;
    res = min(w[1][p] + w[p][q] + w[q][n], w[1][q] + w[q][p] + w[p][n]);
    printf("%d", res >= 1e8 ? -1 : res);
    return 0;
}

1507번: 궁금한 민호

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


$O(n^3)$

두 지점 i,j에 대해 k를 거쳐가서 가능한 최소 거리가 원래의 i-j 최소거리와 같다면 원래의 i-j 최소경로를 삭제해도 된다.(그보다 작다면 모순이다.)
이것을 삭제해도 i-k-j 경로가 남아 있기 때문에 아무런 지장이 없다. 최대한 삭제한 다음 모든 도로 시간합을 출력하자.


#include<stdio.h>
#define min(x,y) x<y?x:y
int n, s, c[20][20];
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            scanf("%d", &c[i][j]), s += c[i][j];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            int mini = 0x7fffffff;
            for (int k = 0; k < n; k++)
                if (i != k&&j != k) mini = min(mini, c[i][k] + c[j][k]);
            if (mini < c[i][j]) {
                puts("-1");
                return 0;
            }
            else if (mini == c[i][j]) s -= c[i][j];
        }
    }
    printf("%d", s / 2);
    return 0;
}