2017년 9월 26일 화요일

(Dinic algorithm) 가구 공장

8904 가구 공장 https://www.acmicpc.net/problem/8904
13161 분단의 슬픔 https://www.acmicpc.net/problem/13161

1. 가구 공장
SCPC 2회 예선에 나왔던 "구두제작"이라는 문제와 거의 비슷한 문제다.
다만 여기서는 Edmonds Karp algorithm을 사용하면 TLE가 나므로 dinic algorithm으로 풀고
제작 스케줄 또한 구해야 한다.

구두제작에서 풀었던것과 같이 모델링하는것은 변함없다.
src ↔ 직원 ↔ 시간 ↔ 가구 sink

최대 유량이 흘러지면 가구를 만들 수 있는것이다.
그리고 스케줄을 구해야하는데 문제를 잘못읽어 뻘짓을 좀 했다.
중첩되는 부분이있으면 그 부분을 통일 시켜주면 된다.
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;
#define INF 0x3f3f3f3f
struct Save {
    int l, r;
    int man;
    Save(int ll, int rr, int mm) :l(ll), r(rr), man(mm) {}
};
int N, M;    //직원, 가구
int C[666][666], f[666][666];
int level[666];
int work[666];
vector<vector<int>> adj;
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(int src, int sink) {
    memset(level, -1sizeof level);
    queue<int> q;
    level[src] = 0;
    q.push(src);
 
    while (!q.empty()) {
        int here = q.front();
        q.pop();
 
        for (int 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, int const 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(C[here][next] - f[here][next], flow));
            if (get > 0) {
                f[here][next] += get;
                f[next][here] -= get;
                return get;
            }
        }
    }
    return 0;
}
int main() {
    int T;
    scanf("%d"&T);
    while (T--) {
        memset(C, 0sizeof C);
        memset(f, 0sizeof f);
        int sum = 0, src = 0, sink = 665;
        scanf("%d%d"&N, &M);
        adj = vector<vector<int>>(666vector<int>());
    
        int minS = 0x3f3f3f3f, maxD = 0;
        for (int m = 1;m <= M;m++) {
            int s, w, d;
            scanf("%d%d%d"&s, &w, &d);
            sum += w;
            minS = min(minS, s);
            maxD = max(maxD, d);
            for (int i = s;i < d;i++
                add_edge(i, 500 + m, 1);        //시간 <-> 가구
            add_edge(500 + m, sink, w);            //가구 <-> sink
        }
        for (int n = 1;n <= N;n++) {
            add_edge(src, 500 + M + n, maxD - minS);        //src <-> 직원
            for (int i = minS;i < maxD;i++)
                add_edge(500 + M + n, i, 1);                //직원 <-> 시간
        }
        int maxflow = 0;
        while (bfs(src, sink)) {
            memset(work, 0sizeof work);
            while (1) {
                int flow = dfs(src, sink, INF);
                if (flow == 0break;
                maxflow += flow;
            }
        }
        if (maxflow == sum) {
            vector<vector<Save>> tmp(M + 1vector<Save>());
            for (int m = 1;m <= M;m++) {
                for (int t = minS;t < maxD;t++) {
                    if (f[t][500 + m]) {
                        f[t][500 + m] = 0;
                        tmp[m].push_back(Save(t, t + 1-1));
                    }
                }
            }
            vector<vector<Save>> ans(M + 1vector<Save>());
            for (int m = 1;m <= M;m++) {
                if (tmp[m].size() == 1) {
                    ans[m].push_back(tmp[m][0]);
                }
                else if(!tmp[m].empty()){
                    int l = tmp[m][0].l;
                    int Size = (int)tmp[m].size();
                    for (int n = 1;n < Size;++n) {
                        Save &here = tmp[m][n], &prev = tmp[m][n - 1];
                        if (n == Size - 1) {
                            if (here.l != prev.r) {
                                ans[m].push_back(Save(l, prev.r, prev.man));
                                ans[m].push_back(Save(here.l, here.r, here.man));
                            }
                            else ans[m].push_back(Save(l, here.r, here.man));
                            break;
                        }
                        if (here.l == prev.r) continue;
                        else {
                            ans[m].push_back(Save(l, prev.r, prev.man));
                            l = here.l;
                        }
                    }
                }
            }
            for (int m = 1;m <= M;m++) {
                printf("%d ", (int)ans[m].size());
                for (int n = 0;n < ans[m].size();n++) {
                    printf("%d %d ", ans[m][n].l, ans[m][n].r);    
                }
                printf("\n");
            }
        }
        else printf("0\n");
    }
    return 0;
}
cs

2. 분단의 슬픔
최소 컷을 구해야하는 문제이다.
완전 그래프를 만들고 src에서 bfs를 돌리며 
유량을 더 보낼 수 있는 쪽이 A진영에 포함되있는 것이다.
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
#define INF 987654321
int N;
vector<int> adj[503];
int C[503][503], f[503][503];
int level[503];
int work[503];
bool bfs(int src, int sink) {
    queue<int> q;
    memset(level, -1sizeof level);
    level[src] = 0;
    q.push(src);
 
    while (!q.empty()) {
        int here = q.front();
        q.pop();
 
        for (int &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, int const sink, int flow) {
    if (here == sink) return flow;
    for (int &= work[here]; i < adj[here].size(); i++) {
        int next = adj[here][i];
        if (level[next] == level[here] + 1 && C[here][next] - f[here][next] > 0) {
            int get = dfs(next, sink, min(C[here][next] - f[here][next], flow));
            if (get > 0) {
                f[here][next] += get;
                f[next][here] -= get;
                return get;
            }
        }
    }
    return 0;
}
int main() {
    scanf("%d"&N);
    int src = 0, sink = N + 1;
    for (int n = 1;n <= N;n++) {
        int tmp;
        scanf("%d"&tmp);
        if (tmp == 1) {
            C[src][n] = INF;
            adj[src].push_back(n);
            adj[n].push_back(src);
        }
        else if (tmp == 2) {
            C[n][sink] = INF;
            adj[n].push_back(sink);
            adj[sink].push_back(n);
        }
    }
    for (int n = 1;n <= N;n++) {
        for (int m = 1;m <= N;m++) {
            scanf("%d"&C[n][m]);
            if (n != m) adj[n].push_back(m);
        }
    }
 
    int maxflow = 0;
    while (bfs(src,sink)) {
        memset(work, 0sizeof work);
        while (1) {
            int flow = dfs(src, sink, INF);
            if (flow == 0break;
            maxflow += flow;
        }
    }
    printf("%d\n", maxflow);
    vector<bool> visit(503false);
    queue<int> q;
    q.push(src);
    visit[src] = true;
    while (!q.empty()) {
        int here = q.front();
        q.pop();
        for (int next : adj[here]) {
            if (!visit[next] && C[here][next] - f[here][next] > 0) {
                visit[next] = true;
                q.push(next);
            }
        }
    }
    for (int n = 1;n <= N;n++if (visit[n]) printf("%d ", n);
    printf("\n");
    for (int n = 1;n <= N;n++if (!visit[n]) printf("%d ", n);
    printf("\n");
    return 0;
}
cs

(DP - 최적의 순서로 맞추기) 13448 SW 역량 테스트

13448 SW 역량 테스트 https://www.acmicpc.net/problem/13448
Codeforces #436 (Div.2) E - Fire http://codeforces.com/contest/864/problem/E

dp는 최적 부분구조를 이뤄야 시행될 수 있다.
만약 주어지는 입력값이 작으면 bitmask dpset 또는 map에 저장시켜 풀 수 있지만
그 값이 크면 대부분 TLE가 발생한다.
그러므로 최적 부분구조를 만드는것이 중요하다.

1. SW 역량 테스트
T분 동안 진행되는 시험에 N개의 문제가 나온다.
$i$번 문제를 $t$분만에 풀면 $M_{i}-t*P_{i}$점을 얻게된다.
$i$번 문제 푸는 시간은 $R_{i}$이다.
얻을 수 있는 점수의 최댓값을 구하는 문제다.

얼마전에 삼성 SW역량 테스트 A형을 봤는데 생각나게끔 하는 문제다.
사람이 생각보다 많이 왔고 A형이라 매우 쉽게 나왔다.

어쨌든 문제로 돌아가면 주어진 문제들의 정보를 입력 그대로 푼다면 위에서 언급한
bitmask dpset정도를 사용해야한다. (하지만 TLE)

따라서 어떻게든 정렬을 해야하는데 수식을 전개하면 아주 놀라운 일이 벌어진다.
https://www.acmicpc.net/board/view/11824
(수식 전개는 별로어렵지 않으니 직접해보는것 추천)

요약 : $R_{i}/P_{i} < R_{j}/P_{j}$ 이면 $i$를 푼 후 $j$를 푸는것이 낫다.
따라서 정렬한 후에 dp로 풀면된다.
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
struct Save { 
    long long M, P, R; 
    double pivot;
};
int N, T;
Save save[51];
long long dp[51][100001];
long long solve(int idx, long long time) {
    if (idx == N) return 0;
    long long &ret = dp[idx][time];
    if (ret != -1return ret;
    ret = 0;
    if (idx + 1 <= N && time + save[idx].R <= T)
        ret = max(ret, solve(idx + 1, time + save[idx].R) + save[idx].M - (time + save[idx].R)*save[idx].P);
    if (idx + 1 <= N)
        ret = max(ret, solve(idx + 1, time));
    return ret;
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d%d"&N, &T);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].M);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].P);
    for (int n = 0;n < N;n++scanf("%lld"&save[n].R);
    for (int n = 0;n < N;n++) save[n].pivot = save[n].R / (double)save[n].P;
    sort(save, save + N, [](Save const &a, Save const &b)->bool {
        return a.pivot < b.pivot;
    });
    printf("%lld\n", solve(00));
    return 0;
}
cs

2. Codeforces #436 (Div.2) E - Fire
집에 불이 났다.
집에는 값어치가 매겨진 물품들이 있고 그 물품 $i$를 구출하는데 
걸리는 시간 $t_{i}$, 다 타버리는 시간 $d_{i}$, 물품의 가치 $p_{i}$가 주어진다.
구조할 수 있는 물품의 가치의 합을 최대화하도록 만들고 어떤 물품을 구조했는지 구하는 문제다.

이 문제도 최적 부분구조를 만들어야한다.
바로 $d_{i}$를 최소화 하는것부터 구출하면 최적 부분구조를 이룰 수 있다.
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
using namespace std;
#define mp(a,b) make_pair(a,b)
#define mt(a,b,c) mp(a,mp(b,c))
#define mf(a,b,c,d) mp(mp(a,b),mp(c,d))
typedef long long ll;
struct Save {
    int t, d, p;
    int idx;
};
int N;
Save arr[101];
vector<int> ans;
int dp[101][2002];
int solve(int idx, int time) {
    if (idx >= N) return dp[idx][time] = 0;
    int &ret = dp[idx][time];
    if (ret != -1return ret;
    ret = 0;
    if (time + arr[idx].t < arr[idx].d) {
        ret = max(ret, solve(idx + 1, time + arr[idx].t) + arr[idx].p);
    }
    ret = max(ret, solve(idx + 1, time));
    return ret;
}
void reconstruct(int idx, int time) {
    if (idx >= N) return;
    int here = dp[idx][time];
    if (time + arr[idx].t < arr[idx].d && here == dp[idx + 1][time + arr[idx].t] + arr[idx].p) {
        ans.push_back(idx);
        reconstruct(idx + 1, time + arr[idx].t);
        return;
    }
    reconstruct(idx + 1, time);
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d"&N);
    for (int n = 0;n < N;n++scanf("%d%d%d"&arr[n].t, &arr[n].d, &arr[n].p), arr[n].idx = n;
    sort(arr, arr + N, [](Save &a, Save &b)->bool {
        return a.d == b.d ? a.t < b.t : a.d < b.d;
    });
    printf("%d\n", solve(00));
    reconstruct(00);
    printf("%d\n", ans.size());
    for (int &next : ans)printf("%d ", arr[next].idx + 1);
    return 0;
}
cs