ACM Computer Factory POJ - 3436 (最大流+建图+Dinic+点权与边权之间的转换)

目录

传送门

点权转化为边权


传送门

题意:题意迷得很,最后也是看网友的才看懂。

每台计算机有p(<=10)个零件,一个公司有n(<=50)个工厂。每个工厂加工一些半成品,输入(得到)一些指定的零件,最多能够输出指定套(指定的是总输出上限,最多不能输出输入套)指定的文件。

n行,第 i 行第一个值是第 i 个工厂的总输出的上限 ,然后是p个数表示输入的零件,0表示一定不能有该零件,1表示一定要有该零件,2表示无所谓,接着是输出的零件,0表示有该零件,1表示没有。

问每多能造出多少台电脑(当所有输出都有,即都为1的时候表示可以造一台电脑),最开始什么也没有(无中生有)。

题解:建图(点权转化为边权)+最大流Dinic

1).有一个点权,我们转化为边权。

点权转化为边权

2)建图注意,超级源点s=0,超级汇点t=2*n+1。超级源点连接所有输入要求全为0的工厂,所有输出要求全为1的工厂连接超级汇点。

除此之外:任意两个工厂之间满足要求则可以互相连接。

说明:1)感觉真的难建图,甚至有时候觉得题目条件没给玩(这个可能只是这个题的错觉)

2)最开始什么也没有;输出全为1表示可以造一台电脑。

代码:

(ps:等刷了一些题之后要总结一个更简单,更正确的模板——理解的更透彻)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <string>
#include <vector>
#define read(x) scanf("%d", &x)
#define print(a, c) printf("%d%c", a, c)
#define pb push_back
using namespace std;
const int N = 100 + 10;  //节点数2*n
const int M = 1e3 + 10;  //边最多n*n+2*n=2500+100
const int INF = 1e9 + 10;

int p, n;                  // p个零件,n个工厂
int a[N][23];              //数量,以及入,出
int s, t;                  //源点与汇点
int head[N], now[N], cnt;  //注意最后节点编号为 0~2*n+1
struct Edge {
    int x, to, w, next;
    Edge(int x = 0, int to = 0, int w = 0, int next = 0)
        : x(x), to(to), w(w), next(next) {}
} e[M];
vector<int> dp;  //记录下特定的边—— out_x -> in_y
struct node {
    int x, y, w;
    node(int x = 0, int y = 0, int w = 0) : x(x), y(y), w(w) {}
};
vector<node> ans;
void add(int u, int v, int w) {
    e[cnt] = Edge(u, v, w, head[u]), head[u] = cnt++;
    e[cnt] = Edge(v, u, 0, head[v]), head[v] = cnt++;
}
//点权——拆点:分为入点(1~n)和出点(n+1~n+n),同时将入点和出点连接,权值为点权
void build() {
    for (int i = 1; i <= n; i++) {
        add(i, n + i, a[i][1]);
        bool in = true, out = true;
        for (int k = 1; k <= p; k++)
            if (a[i][k + 1] == 1) in = false;
        for (int k = 1; k <= p; k++)
            if (a[i][p + 1 + k] == 0) out = false;
        if (in) add(s, i, INF);       //超级源点
        if (out) add(i + n, t, INF);  //超级汇点
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            bool ok = true;
            for (int k = 1; k <= p; k++) {
                if (a[j][1 + k] == 2) continue;
                //注意下标,注意是p还是n
                if (a[i][1 + p + k] == 1 && a[j][1 + k] == 0) ok = false;
                if (a[i][1 + p + k] == 0 && a[j][1 + k] == 1) ok = false;
            }
            if (ok) dp.pb(cnt), add(i + n, j, INF);  //答案只取这里的点
        }
    }
}
int dep[N];
bool bfs() {
    for (int i = 0; i <= 2 * n + 1; i++) dep[i] = INF;  //还是INF好用
    queue<int> q;
    q.push(s), dep[s] = 0;
    now[s] = head[s];
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = now[x]; ~i; i = e[i].next) {
            int v = e[i].to;
            now[v] = head[v];
            if (dep[v] == INF && e[i].w) {
                dep[v] = dep[x] + 1;
                if (v == t) return true;
                q.push(v);
            }
        }
    }
    // for (int i = s; i <= t; i++) cout << i << " " << dep[i] << endl;
    return false;
}
int dfs(int x, int flow) {
    if (x == t) return flow;
    int ans = 0;
    for (int i = now[x]; ~i && flow; i = e[i].next) {
        int v = e[i].to;
        now[x] = i;
        //满足基本条件再能继续。dep[v]=-1无解,dep[x]=-1呢,除了dep[s],其他不可能为0。所以另dep[v]=-1是没有大问题的
        //一个算法,想要运用好,需要注意的东西是很多的!所以acm不是能纯抄模板就能打好的,必须要掌握很多算法思维。
        //还是建议弄成INF吧(虽然都可以)
        if (e[i].w && dep[v] == dep[x] + 1) {
            int tmp = dfs(v, min(flow, e[i].w));
            if (tmp == 0) dep[v] = INF;  //剪枝,结束的就不搞了
            e[i].w -= tmp;
            e[i ^ 1].w += tmp;
            ans += tmp;
            flow -= tmp;
        }
    }
    return ans;
}
int Dinic() {
    int res = 0;
    while (bfs()) {
        // for (int i = 0; i <= 2 * n + 1; i++) now[i] = head[i];
        //就不放在的bfs了。费用流需要每次都更新就放在循环里面
        //最终证明只能够放在bfs或者循环当中,不能放在外面
        res += dfs(s, INF);
    }

    return res;
}
void init() {
    memset(head, -1, sizeof(head));
    cnt = 0, ans.clear(), dp.clear();
    s = 0, t = 2 * n + 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= 2 * p + 1; j++) read(a[i][j]);
    build();  //建图也是一个难点
}
signed main() {
    while (cin >> p >> n) {
        init();

        int maxflow = Dinic();
        print(maxflow, ' ');
        for (int i = 0; i < dp.size(); i++) {
            //反向边有值,dp[i]为正向边
            if (e[dp[i] ^ 1].w)
                ans.pb(node(e[dp[i]].x - n, e[dp[i]].to, e[dp[i] ^ 1].w));
            //注意出边是+n了的,现在就要减回来
        }
        print(ans.size(), '\n');
        for (int i = 0; i < ans.size(); i++)
            printf("%d %d %d\n", ans[i].x, ans[i].y, ans[i].w);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值