페이지

레이블이 Network Flow Problem인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Network Flow Problem인 게시물을 표시합니다. 모든 게시물 표시

13503번: 최소 체인 커버

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

최대 이분 매칭을 구해서 풀 수 있다.

모든 정점 i에 대해 source, sink와 각각 연결된 두 개의 정점 i, i'를 만들고
주어지는 간선 u -> v 마다 만들고 있는 이분 그래프에서 간선 u -> v' 을 추가한다.
이 그래프에서의 매칭을 원래 그래프에서 체인에 속한 간선들과 대응시킬 수 있다.
(체인 수) = n - (체인에 속한 간선 수) 이므로 (최소 체인 수) = n - (최대 매칭 수) 이다.

시간복잡도는 $O(nm)$

#include<cstdio>
#include<vector>
using namespace std;
int n, m, vis[10001], rev[10001], t, res;
vector<int> adj[10001];
int f(int h) {
    vis[h] = t;
    for (auto it : adj[h]) if (!rev[it] || vis[rev[it]] ^ t && f(rev[it])) {
        rev[it] = h;
        return 1;
    }
    return 0;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0, x, y; i < m; i++) {
        scanf("%d%d", &x, &y);
        adj[x].push_back(y);
    }
    res = n;
    for (t = 1; t <= n; t++) res -= f(t);
    printf("%d", res);
    return 0;
}

11378번: 열혈강호 4

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

이분 매칭 문제로 만들어 풀 수 있다.
먼저, 직원과 일을 일대일 매칭시킨다. 그리고 나서 벌점을 배분하는데, 매칭이 되지 않은 일 중 해당 일을 할 수 있는 직원(처음에 매칭되었던 직원이라도 상관없다.)이 있다면 그러한 직원 아무에게나 1점의 벌점을 매기고 해당 일을 하도록 할 수 있다.
고로 답은 min( (일대일 매칭 수) + k, (직원 누군가 할 수 있는 일의 개수) )

시간복잡도는 $O(V^3)$ // V = n+m

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
int vis[1001], rev[1001], n, m, k, ck[1001], cnt, c;
vector<int> adj[1001];
int dfs(int h) {
    vis[h] = c;
    for (int it : adj[h]) if (!rev[it] || vis[rev[it]] ^ c && dfs(rev[it])) {
        rev[it] = h;
        return 1;
    }
    return 0;
}
int main() {
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1, x, y; i <= n; i++) for (scanf("%d", &x); x--;) {
        scanf("%d", &y);
        adj[i].push_back(y);
        cnt += !ck[y]++;
    }
    for (c = 1; c <= n; c++) k += dfs(c);
    printf("%d", min(k, cnt));
    return 0;
}

13161번: 분단의 슬픔

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

임의의 i, j에 대해 용량이 w[i, j]인 i->j 간선을 생성한다.
A 진영에 속한 i번 사람에 대해 용량이 inf인 source -> i 간선을 생성한다.
B 진영에 속한 i번 사람에 대해 용량이 inf인 i -> sink 간선을 생성한다.
이 그래프에서 최대 유량이 답이 된다.
그 이유는 최대 유량 = 최소 컷이고, 컷은 곧 두 진영으로 나누었을 때 슬픔 정도이기 때문이다.

Dinic's algorithm을 사용해야 한다.

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

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
int fl[502][502], n, res, lv[502], c[502];
bool bfs() {
    fill(lv + 1, lv + n + 2, -1);
    queue<int> q;
    q.push(0);
    while (!q.empty()) {
        int h = q.front();
        q.pop();
        for (int i = 0; i <= n + 1; i++) if (!~lv[i] && fl[h][i]) lv[i] = lv[h] + 1, q.push(i);
    }
    return ~lv[n + 1];
}
int dfs(int h, int x) {
    if (h == n + 1) return x;
    for (int &i = c[h]; i <= n + 1; i++) if (lv[h] < lv[i] && fl[h][i]) {
        int ret = dfs(i, min(x, fl[h][i]));
        if (ret) {
            fl[h][i] -= ret;
            fl[i][h] += ret;
            return ret;
        }
    }
    return 0;
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d", &x);
        if (x == 1) fl[0][i] = 1e9;
        if (x == 2) fl[i][n + 1] = 1e9;
    }
    for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) scanf("%d", fl[i] + j);
    while (bfs()) {
        fill(c, c + n + 2, 0);
        for (int ret; ret = dfs(0, 1e9);) res += ret;
    }
    printf("%d\n", res);
    for (int i = 1; i <= n; i++) if (~lv[i]) printf("%d ", i);
    puts("");
    for (int i = 1; i <= n; i++) if (!~lv[i]) printf("%d ", i);
    puts("");
    return 0;
}

6241번: Dining

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

최대 유량을 구하는 문제로 바꿔 풀 수 있다.

소가 i번이면 용량 1의 i - i' 간선을 추가한다.
i번 소가 food fi를 좋아하면 용량 1의 source -> fi, fi -> i 간선을 추가한다.
i번 소가 drink di를 좋아하면 용량 1의 i' -> di, di -> sink 간선을 추가한다.
답은 이 그래프에서 최대 유량이 된다.

시간복잡도는 $O(V^3)$ // V = N + F + D

#include<cstdio>
#include<cstring>
int n, f, d, c[402][402], res, vis[402];
int dfs(int h) {
    vis[h] = 1;
    for (int i = 0; i < 402; i++) if (c[h][i] && (i == 401 || !vis[i] && dfs(i))) {
        c[h][i]--;
        c[i][h]++;
        return 1;
    }
    return 0;
}
int main() {
    scanf("%d%d%d", &n, &f, &d);
    for (int i = 1, fi, di, x; i <= n; i++) {
        scanf("%d%d", &fi, &di);
        for (int j = 0; j < fi; j++) scanf("%d", &x), c[0][200 + x] = c[200 + x][i] = 1;
        for (int j = 0; j < di; j++) scanf("%d", &x), c[100 + i][300 + x] = c[300 + x][401] = 1;
        c[i][100 + i] = 1;
    }
    for (int ret; ret = dfs(0);) memset(vis, 0, sizeof(vis)), res += ret;
    printf("%d", res);
    return 0;
}

13166번: 범죄 파티

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


주어진 문제는 친구 및 용의자를 노드, 임계값이 x이하인 관계들을 간선으로 나타내어 그래프를 만들었을 때, 모든 용의자에 대해 매칭이 가능한지 판단하는 문제로 바꿀 수 있다.
각 노드의 최대 차수는 2이므로 가능한 각 연결 그래프의 모양은 일자 아니면 링형 밖에 없다. 이러한 모양의 연결 그래프에서는 노드 수가 짝수이면 모든 노드의 매칭이 가능하고 홀수이면 불가능하다.
임계값이 작은 관계부터 보며 간선을 추가한다. 모든 연결그래프의 노드 수가 짝수가 되었을 때, 직전에 추가한 간선에 관한 임계값이 답이 된다. 모든 간선을 추가하고도 모든 연결그래프의 노드 수가 짝수가 되지 않는다면 불가능하다.

시간복잡도는 $O(n\lg n)$

#include<cstdio>
#include<algorithm>
using namespace std;
int n, sz[600001], p[600001], r;
struct st {
    int x, y, c;
}l[400001];
int f(int x) { return x^p[x] ? p[x] = f(p[x]) : x; }
int main() {
    scanf("%d", &n);
    for (int i = 1, a, b, c, d; i <= n; i++) {
        scanf("%d%d%d%d", &a, &b, &c, &d);
        l[i] = { i,a + n,b };
        l[i + n] = { i,c + n,d };
    }
    for (int i = 1; i <= 6 * n; i++) sz[i] = 1, p[i] = i;
    sort(l + 1, l + 2 * n + 1, [](st i, st j) {return i.c < j.c; });
    r = n;
    for (int i = 1; i <= 2 * n; i++) {
        int ra = f(l[i].x), rb = f(l[i].y);
        r -= sz[ra] & sz[rb];
        if (!r) printf("%d", l[i].c), exit(0);
        sz[ra] ^= sz[rb];
        p[rb] = ra;
    }
    puts("-1");
    return 0;
}


비용을 기준으로 파라매트릭 서치를 해볼 수 있다.
임계값이 정한 비용 이하인 친구 - 용의자 관계를 가지고 이분 그래프를 만든 뒤, Hopcroft-Karp algorithm으로 이분 매칭을 해본다.

시간복잡도는 $O(n^{1.5}\lg L)$ // L: 임계값의 최댓값

#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int MXN = 2e5;
int n, lv[MXN + 1], rev[MXN * 2 + 1], mc[MXN + 1], mid;
vector<pair<int, int> > adj[MXN + 1];
bool bfs() {
    memset(lv, -1, sizeof(lv));
    queue<int> q;
    bool flag = false;
    for (int i = 1; i <= n; i++) if (!mc[i]) q.push(i);
    while (!q.empty()) {
        int h = q.front();
        q.pop();
        for (auto it : adj[h]) if (it.second <= mid) {
            if (!rev[it.first]) flag = true;
            else if (!~lv[rev[it.first]]) lv[rev[it.first]] = lv[h] + 1, q.push(rev[it.first]);
        }
    }
    return flag;
}
bool dfs(int h) {
    for (auto it : adj[h]) if (it.second <= mid && (!rev[it.first] || lv[h] < lv[rev[it.first]] && dfs(rev[it.first]))) {
        rev[it.first] = h;
        return true;
    }
    lv[h] = 0;
    return false;
}
bool f() {
    memset(rev, 0, sizeof(rev));
    memset(mc, 0, sizeof(mc));
    int r = 0;
    while (bfs()) for (int i = 1; i <= n; i++) if (!mc[i] && dfs(i)) mc[i] = 1, r++;
    return r == n;
}
int main() {
    scanf("%d", &n);
    for (int i = 1, a, b, c, d; i <= n; i++) scanf("%d%d%d%d", &a, &b, &c, &d), adj[i] = { { a,b },{ c,d } };
    int low = 0, up = 1e6;
    while (low <= up) {
        mid = (low + up) / 2;
        f() ? up = mid - 1 : low = mid + 1;
    }
    printf("%d", low > 1e6 ? -1 : low);
    return 0;
}

1733번: 등번호

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

각 참가자마다 두 번호와 간선을 만들어 이분 그래프를 만든다.
이분 매칭을 해서 매칭 수가 n보다 작으면 불가능하고 n이면 각 참가자마다 매치된 번호를 출력한다.

Edmonds-Karp Algorithm을 사용하는 경우에는 visited 체크를 위해 배열을 초기화하는 대신에 탐색마다 다른 수를 덮어 씌어 TLE을 피해야 한다.

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

#include<cstdio>
#include<vector>
using namespace std;
const int MX = 1e6;
int n, r, vis[MX + 1], rev[MX + 1], mc[MX + 1], cnt;
vector<int> adj[MX + 1];
int dfs(int h) {
    if (vis[h] == cnt) return 0;
    vis[h] = cnt;
    for (int it : adj[h]) if (!rev[it] || dfs(rev[it])) {
        mc[h] = it;
        rev[it] = h;
        return 1;
    }
    return 0;
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x, y; i <= n; i++) scanf("%d%d", &x, &y), adj[i] = { x,y };
    for (int i = 1; i <= n; i++) ++cnt, r += dfs(i);
    if (r < n) puts("-1");
    else for (int i = 1; i <= n; i++) printf("%d\n", mc[i]);
    return 0;
}


매칭을 빠르게 구하기 위해 Hopcorft-Karp 알고리즘을 쓴다.
시간복잡도는 $O(n\sqrt n)$

#include<cstdio>
#include<vector>
using namespace std;
const int MX = 1e6;
int n, mc[MX + 1], lv[MX + 1], rev[MX + 1], q[MX], r;
vector<int> adj[MX + 1];
int bfs() {
    int flag = 0, h = 0, t = 0;
    for (int i = 1; i <= n; i++) {
        if (!mc[i]) q[t++] = i, lv[i] = 0;
        else lv[i] = -1;
    }
    for (; h < t; h++) for (int it : adj[q[h]]) {
        if (!rev[it]) flag = 1;
        else if (!~lv[rev[it]]) lv[rev[it]] = lv[q[h]] + 1, q[t++] = rev[it];
    }
    return flag;
}
int dfs(int h) {
    for (int it : adj[h]) if (!rev[it] || lv[h] < lv[rev[it]] && dfs(rev[it])) {
        mc[h] = it;
        rev[it] = h;
        return 1;
    }
    lv[h] = 0;
    return 0;
}
int main() {
    scanf("%d", &n);
    for (int i = 1, x, y; i <= n; i++) scanf("%d%d", &x, &y), adj[i] = { x,y };
    while (bfs()) for (int i = 1; i <= n; i++) if (!mc[i]) r += dfs(i);
    if (r < n) puts("-1");
    else for (int i = 1; i <= n; i++) printf("%d\n", mc[i]);
    return 0;
}

3736번: System Engineer

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

이분 매칭 문제이다. Hopcroft–Karp Algorithm 알고리즘을 써야 TLE를 피할 수 있다.

시간복잡도는 $O(n^{2.5})$

#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
vector<int> adj[10000];
int n, ck[10000], rev[10000], lv[10000];
bool BFS() {
    queue<int> q;
    fill(lv, lv + n, -1);
    for (int i = 0; i < n; i++) if (!ck[i]) q.push(i), lv[i] = 0;
    int flag = 0;
    while (!q.empty()) {
        int h = q.front();
        q.pop();
        for (int it : adj[h]) {
            if (!~rev[it]) flag = 1;
            else if (!~lv[rev[it]]) lv[rev[it]] = lv[h] + 1, q.push(rev[it]);
        }
    }
    return flag;
}
int DFS(int h) {
    for (int it : adj[h]) if (!~rev[it] || lv[h] < lv[rev[it]] && DFS(rev[it])) {
        rev[it] = h;
        return 1;
    }
    lv[h] = 0;
    return 0;
}
int main() {
    while (~scanf("%d", &n)) {
        int r = 0;
        for (int i = 0, x, m, y; i < n; i++) {
            scanf("%d: (%d)", &x, &m);
            adj[x].clear();
            for (int j = 0; j < m; j++) scanf("%d", &y), adj[x].push_back(y - n);
        }
        fill(rev, rev + n, -1);
        fill(ck, ck + n, 0);
        while (BFS()) {
            for (int i = 0; i < n; i++) if (!lv[i] && DFS(i)) ck[i] = 1, r++;
        }
        printf("%d\n", r);
    }
    return 0;
}

2051번: 최소 버텍스 커버

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

$O(n^2m)$

#include<cstdio>
#include<vector>
using namespace std;
int n, m, r[1001], vis[1001], res, ck[1001], acnt, bcnt, b[1001];
vector<int> adj[1001];
int f(int h) {
    if (vis[h]) return 0;
    vis[h] = 1;
    for (int it : adj[h]) {
        if (!r[it] || f(r[it])) {
            r[it] = h;
            return 1;
        }
    }
    return 0;
}
void g(int h) {
    if (vis[h]) return;
    vis[h] = 1;
    acnt++;
    for (int it : adj[h]) bcnt += !b[it]++, g(r[it]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, x, y; i <= n; i++)
        for (scanf("%d", &x); x--;) scanf("%d", &y), adj[i].push_back(y);
    for (int i = 1; i <= n; i++) {
        if (f(i)) ck[i] = 1, res++;
        for (int j = 1; j <= n; j++) vis[j] = 0;
    }
    for (int i = 1; i <= n; i++) if (!ck[i]) g(i);
    printf("%d\n%d", res, n - acnt);
    for (int i = 1; i <= n; i++) if (!vis[i]) printf(" %d", i);
    printf("\n%d", bcnt);
    for (int i = 1; i <= n; i++) if (b[i]) printf(" %d", i);
    return 0;
}

1867번: 돌멩이 제거

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


$O(nk)$

(x,y)가 1일 떄 x->y 간선을 만들어 보면 이분 그래프를 만들 수 있으며 이 그래프에서 minimum vertex cover의 개수가 문제의 답이 된다.
이 수는 쾨닉의 정리에 따라 최대 이분 매칭 수와 같다.

#include<cstdio>
#include<vector>
using namespace std;
int n, k, p[501], vis[501], r;
vector<int> adj[501];
int dfs(int h) {
    if (!vis[h]) {
        vis[h] = 1;
        for (auto it : adj[h]) if (!p[it] || dfs(p[it])) {
            p[it] = h;
            return 1;
        }
    }
    return 0;
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 0, x, y; i < k; i++) {
        scanf("%d%d", &x, &y);
        adj[x].push_back(y);
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) vis[j] = 0;
        r += dfs(i);
    }
    printf("%d", r);
    return 0;
}

2414번: 게시판 구멍 막기

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


$O(n^2m^2)$

가로, 세로로 연속적으로 구멍뚤린 부분을 하나의 노드로 보고 같은 지점을 공유하는 노드끼리 이어주면 이분 그래프를 만들 수 있다.
이 그래프에서 minimum vertex cover가 곧 문제의 정답이고 이는 Kőnig's theorem에 따라 최대 매칭을 구하는 문제와 동치이다.

#include<cstdio>
#include<vector>
using namespace std;
int r[50][50], c[50][50], rcnt, ccnt, n, m, vis[2500], p[2500], flow, t;
char s[50][51];
vector<int> adj[2500];
int f(int h) {
    if (vis[h] == t) return 0;
    vis[h] = t;
    for (auto it : adj[h]) if (p[it] == -1 || f(p[it])) {
        p[it] = h;
        return 1;
    }
    return 0;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", s[i]);
        for (int j = 0; j < m; j++) if (s[i][j] == '*') {
            r[i][j] = !j || s[i][j - 1] == '.' ? rcnt++ : r[i][j - 1];
            c[i][j] = !i || s[i - 1][j] == '.' ? ccnt++ : c[i - 1][j];
            adj[r[i][j]].push_back(c[i][j]);
        }
    }
    for (int i = 0; i < ccnt; i++) p[i] = -1;
    for (int i = 0; i < rcnt; i++) ++t, flow += f(i);
    printf("%d", flow);
    return 0;
}

11111번: 두부장수 장홍준 2

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


$O(n^2m^2)$

(x,y) x+y가 홀수인 칸은 A집합, 짝수인 칸은 B집합에 포함된다 하자.
(체스판에서 흰색, 검은색 칸을 생각하자.)
이제 A집합에 있는 칸 x와 B집합에 있는 칸 y 중 인접한 쌍이 있으면
x->y 용량: 1, 비용: x, y 같이 잘랐을 때 값
간선을 추가하자.
그리고
source->A집합, B집합->sink 용량: 1, 비용: 0
간선도 추가한다.
MCMF 문제를 풀어 최대 값어치를 구한다.

#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
const int MAX_N = 50, g[][5] = { { 10,8,7,5,1 },{ 8,6,4,3,1 },{ 7,4,3,2,1 },{ 5,3,2,2,1 },{ 1,1,1,1,0 } }, fx[] = { 0,0,1,-1 }, fy[] = { 1,-1,0,0 };
int n, m, from[MAX_N*MAX_N + 2], cst[MAX_N*MAX_N + 2], ck[MAX_N*MAX_N + 2], res;
char str[MAX_N][MAX_N + 1];
struct st {
    int x, w, c;
};
vector<st> adj[MAX_N*MAX_N + 2];
int main() {
    scanf("%d %d", &n, &m);
    for (int i = 0; i < n; i++) {
        scanf("%s", str[i]);
        for (int j = 0; j < m; j++) if (str[i][j] == 'F') str[i][j]--;
    }
    for (int i = 0; i <n; i++) {
        for (int j = 0; j < m; j++) {
            if ((i + j) % 2) {
                adj[i*m + j + 1].push_back({ n*m + 1,0,1 });
                continue;
            }
            adj[0].push_back({ i*m + j + 1,0,1 });
            for (int k = 0; k < 4; k++) {
                int tx = i + fx[k], ty = j + fy[k];
                if (tx >= 0 && tx < n && ty >= 0 && ty < m)
                    adj[i*m + j + 1].push_back({ tx*m + ty + 1,g[str[i][j] - 'A'][str[tx][ty] - 'A'],1 }),
                    adj[tx*m + ty + 1].push_back({ i*m + j + 1, -g[str[i][j] - 'A'][str[tx][ty] - 'A'],0 });
            }
        }
    }
    for (;;) {
        fill(cst, cst + n*m + 2, -1e9);
        queue<int> q;
        q.push(0); cst[0] = 0;
        while (!q.empty()) {
            int h = q.front();
            q.pop(); ck[h] = 0;
            for (auto it : adj[h]) {
                if (it.c && cst[h] + it.w>cst[it.x]) {
                    cst[it.x] = cst[h] + it.w;
                    from[it.x] = h;
                    if (!ck[it.x]) ck[it.x] = 1, q.push(it.x);
                }
            }
        }
        if (cst[n*m + 1] < 0) break;
        res += cst[n*m + 1];
        for (int i = n*m + 1; i; i = from[i]) {
            for (auto &it : adj[from[i]]) if (it.x == i) it.c--;
            for (auto &it : adj[i]) if (it.x == from[i]) it.c++;
        }
    }
    printf("%d", res);
    return 0;
}