레이블이 이분 매칭인 게시물을 표시합니다. 모든 게시물 표시
레이블이 이분 매칭인 게시물을 표시합니다. 모든 게시물 표시

2017년 12월 27일 수요일

14672 윤호는 마법약 도둑

14672 윤호는 마법약 도둑 https://www.acmicpc.net/problem/14672

윤호는 M개의 N보다 작은 숫자가 적혀져있는 마법약들을 가지고 있다.
마법약은 1을 제외한 약수들중 하나로 추출 할 수 있다.
추출에 성공하려면 추출한 제품들 중에서 어떠한 원료도 공유해서는 안된다.
윤호의 추출할 수 있는 최대 마법약의 개수를 구하는 문제이다.

마법약들을 추출했을 때 모든 마법약들의 원료들이 서로소인 최대 마법약 개수를 구하는 것이다.
gcd를 이용하기에는 구할 방법이 마땅히 없어보였다.

사실상 원료들이 서로소가 되려면 마법약에서 추출할 수 원료들을 소수로 추출하면 된다.
문제는 최대 마법약 개수를 구해야하는데 이분매칭을 이용하면 된다.
네트워크를 나타내면 위와같이 될것이다.
N이 100,000,000이므로 최대 10,000까지의 소수만 구해주면 되는데 여기 실수가 있었다.

10,000보다 큰 수가 들어올 때 10,000보다 작은 소수로 소인수 분해를 하더라도 끝나지 않을 수 있다.
예를들어 104,729와 같은 수이다. 

이런 수는 따로 처리를 해서 저장을 해줘야한다.
밑의 코드에서 소인수 분해를 하고 x > 1인경우가 따로 처리한 경우이다.
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <unordered_map>
#include <queue>
using namespace std;
#define MAX 3500
#define PLUS 2300
#define INF 987654321
int N, M;
int arr[1001];
int pr[10011];
vector<int> prime;
int Size;
vector<int> adj[MAX];
int C[MAX][MAX], f[MAX][MAX];
const int src = MAX - 2, sink = MAX - 1;
int level[MAX], work[MAX];
unordered_map<intint> save;
int cnt = 0;
void init() {
    int L = (int)sqrt(N) + 1;
    for (int n = 2;n*<= L;n++) {
        for (int m = n*n;m <= L;m += n) pr[m] = true;
    }
    for (int n = 2;n <= L;n++if (!pr[n]) prime.push_back(n);
    Size = prime.size();
}
void add_edge(int u, int v, int c) {
    adj[u].push_back(v);
    adj[v].push_back(u);
    C[u][v] = c;
}
bool bfs() {
    memset(level, -1sizeof level);
    queue<int> q;
    q.push(src);
    level[src] = 0;
    while (!q.empty()) {
        int here = q.front();
        q.pop();
        for (auto &next : adj[here]) {
            if (level[next] == -1 && C[here][next] - f[here][next] > 0) {
                level[next] = level[here] + 1;
                q.push(next);
            }
        }
    }
    return level[sink] != -1;
}
int dfs(int here, const int sink, int flow) {
    if (here == sink) return flow;
    for (int &= work[here]; n < adj[here].size(); n++) {
        int next = adj[here][n];
        if (level[next] == level[here] + 1 && C[here][next] - f[here][next] > 0) {
            int get = dfs(next, sink, min(flow, C[here][next] - f[here][next]));
            if (get) {
                f[here][next] += get;
                f[next][here] -= get;
                return get;
            }
        }
    }
    return 0;
}
int main() {
    scanf("%d%d"&N, &M);
    init();
    for (int m = 0, x;m < M;m++) {
        scanf("%d"&x);
        for (int n = 0;n < Size;n++) {
            if (x % prime[n] == 0) {
                add_edge(n, m + PLUS, 1);
                while (x && x % prime[n] == 0) x /= prime[n];
            }
        }
        if (x > 1) {
            if (save[x] == 0) save[x] = ++cnt;
            add_edge(Size + save[x], m + PLUS, 1);
        }
        add_edge(m + PLUS, sink, 1);
    }
    for (int n = 0;n < Size;n++) add_edge(src, n, 1);
    for (int n = 1;n <= cnt;n++) add_edge(src, Size + n, 1);
    int ans = 0;
    while (bfs()) {
        memset(work, 0sizeof work);
        while (1) {
            int get = dfs(src, sink, INF);
            if (!get) break;
            ans += get;
        }
    }
    printf("%d\n", ans);
    return 0;
}
cs

2017년 9월 21일 목요일

2787 흔한 수열 문제

2787 흔한 수열 문제 https://www.acmicpc.net/problem/2787

숨겨진 수열 A가 있다.
  • 1 x y v - x번째 수부터, y번째 수 중 제일 큰 값은 v
  • 2 x y v - x번째 수부터, y번째 수 중 제일 작은 값은 v
위와 같은 query에 대한 답이 주어져있을 때 역으로 수열 A를 구하는 문제이다.

범위값을 조정하면서 풀려고했으나 도저히 감이 잡히지 않아서 문제 분류를 봤는데
flow문제였다.

[x, y] 까지 제일 큰 값이 v였다면 [x, y][1, v]까지 연결해주는 형식이다.
하지만 잘못된 query도 주어지므로 query에 대해 연결되면 안되는 부분들을 체크해주고
query가 끝나면 그 외의 범위들에 대해 정점연결을 해주면 된다.
그 후 이분매칭을 시켜주면 된다.

나중에 다시풀어봐야겠다.
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int N, M;
vector<vector<int>> adj;
int C[444][444], f[444][444];
bool check[222][222];
void add_edge(int u, int v, int c) {
    adj[u].push_back(v);
    adj[v].push_back(u);
    C[u][v] = c;
}
int max_flow(int S, int E) {
    int ret = 0;
    while (1) {
        vector<int> par(2 * N + 5-1);
        queue<int> q;
        q.push(S);
        while (!q.empty()) {
            int here = q.front();
            q.pop();
            for (int next : adj[here]) {
                if (C[here][next] - f[here][next] > 0 && par[next] == -1) {
                    par[next] = here;
                    q.push(next);
                }
            }
        }
        if (par[E] == -1break;
        ret++;
        for (int i = E;i != S;i = par[i])
            f[par[i]][i]++, f[i][par[i]]--;
    }
    return ret;
}
int main() {
    scanf("%d%d"&N, &M);
    for (int m = 0;m < M;m++) {
        int f, l, r, v;
        scanf("%d%d%d%d"&f, &l, &r, &v);
        if (f == 1) {
            for (int n = l;n <= r;n++)
                for (int k = v + 1;k <= N;k++) check[n][k] = true;
        }
        else if (f == 2) {
            for (int n = l;n <= r;n++)
                for (int k = 1;k < v;k++) check[n][k] = true;
        }
        for (int n = 1;n < l;n++) check[n][v] = true;
        for (int n = r + 1;n <= N;n++) check[n][v] = true;
    }
 
    adj = vector<vector<int>>(2 * N + 5vector<int>());
    int src = 0, sink = 2 * N + 4;
    for (int n = 1;n <= N;n++) {
        for (int m = 1;m <= N;m++) {
            if (!check[n][m])
                add_edge(2 * n - 12 * m, 1);
        }
        add_edge(src, 2 * n - 11);
        add_edge(2 * n, sink, 1);
    }
    if (max_flow(src, sink) == N) {
        for (int n = 1;n <= N;n++) {
            for (int m = 1;m <= N;m++)
                if (f[2 * n - 1][2 * m] >= 1) {
                    printf("%d ", m);
                    break;
                }
        }
        printf("\n");
    }
    else printf("-1\n");
    return 0;
}
cs

2017년 7월 18일 화요일

1031 스타대결

1031 스타대결 https://www.acmicpc.net/problem/1031

이 문제를 바로 맞출준 몰랐다.

지민이 팀 N명과 한수 팀 M명이 서로 대결을 한다.
각 사람은 해야하는 경기 수 가 정해져있고 그것을 만족해야한다. (같은 대결은 한번)
대진표가 여러개 존재할때는 사전순으로 앞선것을 선택해야한다. 
($mat[i][j]$가 0인 대진표가 사전순으로 앞선 순서)

대진표가 없는경우는 -1을 출력하는데 이경우는 두가지가 있다.
1. 지민이팀의 해야하는 경기 수 != 한수팀의 해야하는 경기 수
2. Maximum flow != 해야하는 경기 수

문제는 사전순으로 앞선것을 구해야하는데 방법은 다음과 같다.
1. 먼저 대진표를 구해놓는다. 
2. $mat[i][j]$가 1인 곳에서 0이 될 수 있는지 flow를 다시 흘려준다.
(사전순으로 앞선것이기 때문에 이전의 값들은 영향받지 않도록 흘려주어야한다.)
3. 2번과정을 N*M번 반복한다.


#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
int N, M;
vector<int> adj[111];
int C[111][111], f[111][111];
const int src = 0, sink = 110;
void add_edge(int from, int to, int cc) {
    adj[from].push_back(to);
    adj[to].push_back(from);
    C[from][to] = cc;
}
int max_flow() {
    int ret = 0;
    while (1) {
        queue<int> q;
        int par[111];
        fill(par + 1, par + 111-1);
        q.push(src);
        while (!q.empty() && par[sink] == -1) {
            int here = q.front();
            q.pop();
            for (int next : adj[here]) {
                if (C[here][next] - f[here][next] > 0 && par[next] == -1) {
                    q.push(next);
                    par[next] = here;
                }
            }
        }
        if (par[sink] == -1break;
        for (int i = sink;i != src;i = par[i])
            f[par[i]][i]++, f[i][par[i]]--;
        ret++;
    }
    return ret;
}
bool isPossible_erase(int from, int to) {
    f[from][to] = 0, f[to][from] = 1;
    f[src][from]--, f[from][src]++;
    f[to][sink]--, f[sink][to]++;
    int ret = 0;
    while (1) {
        queue<int> q;
        int par[111];
        fill(par + 1, par + 111-1);
        q.push(src);
        while (!q.empty() && par[sink] == -1) {
            int here = q.front();
            q.pop();
            for (int next : adj[here]) {
                //이전의 값들은 영향 받지 않게 예외 처리
                if ((here == from && next == to) || (here == to && next == from)) continue;
                if (here > src && here < from && f[here][next] == 0continue;
                if (here == from && next > N && next < to && f[here][next] == 0continue;
                if (C[here][next] - f[here][next] > 0 && par[next] == -1) {
                    q.push(next);
                    par[next] = here;
                }
            }
        }
        if (par[sink] == -1break;
        for (int i = sink;i != src;i = par[i])
            f[par[i]][i]++, f[i][par[i]]--;
        ret++;
    }
    return ret == 1;        //0으로 만들기 가능함
}
int main() {
    int sum_A = 0, sum_B = 0;
    scanf("%d%d"&N, &M);
    for (int n = 1;n <= N;n++) {
        int A;
        scanf("%d",&A);
        add_edge(src, n, A);
        sum_A += A;
    }
    for (int m = 1;m <= M;m++) {
        int B;
        scanf("%d"&B);
        add_edge(m + N, sink, B);
        sum_B += B;
    }
    if (sum_A != sum_B) {
        printf("-1\n");
        return 0;
    }
    for (int n = 1;n <= N;n++) {
        for (int m = 1;m <= M;m++
            add_edge(n, m + N, 1);
    }
    if (max_flow() != sum_A) {
        printf("-1\n");
        return 0;
    }
    else {
        for (int n = 1;n <= N;n++) {
            for (int m = 1;m <= M;m++) {
                if (f[n][m + N]) {        //1 -> 0으로 만드는게 가능한가?
                    if (!isPossible_erase(n, m + N)){        //불가능 원상복귀
                        f[n][m+N] = 1, f[m+N][n] = 0;
                        f[src][n]++, f[n][src]--;
                        f[m+N][sink]++, f[sink][m+N]--;
                    }
                }
            }
        }
        for (int n = 1;n <= N;n++) {
            for (int m = 1;m <= M;m++
                printf("%d", f[n][m + N]);
            printf("\n");
        }
    }
    return 0;
}
cs