uva 1104 chips challenge 费用流

题目大意:一个芯片可以看成由n*n的插槽组成,每个插槽有三种可能
1. 必须放置一个零件
2. 可以放一个零件也可以不放
3. 不能放置零件
要求
1. 第i行的零件要和第i列的零件一样多
2. 第i行的零件数不能超过总零件数的A / B (A, B为给定的)
求 最多可以放的零件数 - 必须放置的零件数

首先看如何满足第一个条件,假设知道了满足该条件的答案为第i行零件数为xi,那么把每行每列都看成一个点,
源点s向行i连容量为xi的边,行i向列i连容量为inf的边
列i向汇点t连容量为xi的边,如果点(i,j)可以放零件,那么连行i到列j容量为1的边,如果该网络可以满流,
那么这个x即是答案,但是这是一个指数级别的算法,考虑到
在上述的满流网络中增加s->行i->列i->汇点这一通路的容量并不会影响原答案,因此我们可以直接将所有的xi设置为较大的值,
例如n,那么只跑一次网络流就可以了。
不过这样的网络对于所有的输入都会满流,如何让他先流真正的边呢,可以将真正的边设置费用为1,
而额外增加的s->行i->列i->汇点这种假边费用为0,那么如果满流且有费用不为0,
那么即为合法答案,接着看如何满足有的点必须放这一条件,最基本的想法可以搞成下界为1上界为1的边,
不过也可以将必须放的点(i,j)的费用设为较大值L+1,即使所有费用为1的边
全部选择也不会大于L,这样就可以保证先增广必须放置的点所对应的边,最后得到的总费用对L取模即为总零件数,
除以L则为必须放的零件数。最后看第二个条件,在xi的条件下已经得到了
零件总数f,而xi大于等于每一行的零件数,所以可以从高到低枚举xi,第一次满足第二个条件的xi即为答案。
 

#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;

const int N = 100;
const int INF = 1e9;
const int F = 3000;

int map[N][N], C;
 
struct Edge {
    int from, to, cap, flow, cost;
    Edge(int u, int v, int ca, int f, int co) : from(u), to(v), cap(ca), flow(f), cost(co){};
};
 
struct MCMF {
    int n, m, s, t;
    vector<Edge> edges;
    vector<int> G[N];
    int inq[N];//是否在队列中
    int d[N];//距离
    int p[N];//上一条弧
    int a[N];//可改进量
 
    void init(int n) { //初始化 
        this->n = n;
        for(int i = 0; i < n; i++)
            G[i].clear();
        edges.clear();
    }
 
    void addedge(int from, int to, int cap, int cost) { //加边
        edges.push_back(Edge(from, to, cap, 0, cost));
        edges.push_back(Edge(to, from, 0, 0, -cost));
        int m = edges.size();
        G[from].push_back(m - 2);
        G[to].push_back(m - 1);
    }
 
    bool SPFA(int s, int t, int &flow, int &cost) {//寻找最小费用的增广路,使用引用同时修改原flow,cost
        for(int i = 0; i < n; i++)
            d[i] = -INF;
        memset(inq, 0, sizeof(inq));
        d[s] = 0; inq[s] = 1; p[s] = 0; a[s] = INF;
        queue<int> Q;
        Q.push(s);
        while (!Q.empty()) {
            int u = Q.front();
            Q.pop();
            inq[u]--;
            for (int i = 0; i < G[u].size(); i++) {
                Edge& e = edges[G[u][i]];
                if(e.cap > e.flow && d[e.to] < d[u] + e.cost) {//满足可增广且可变短
                    d[e.to] = d[u] + e.cost;
                    p[e.to] = G[u][i];
                    a[e.to] = min(a[u], e.cap - e.flow);
                    if (!inq[e.to]) {
                        inq[e.to]++;
                        Q.push(e.to);
                    }
                }
            }
        }
        if(d[t] == -INF) return false;//汇点不可达则退出
        flow += a[t];
        cost += d[t] * a[t];
        int u = t;
        while (u != s) { //更新正向边和反向边 
            edges[p[u]].flow += a[t];
            edges[p[u] ^ 1].flow -= a[t];
            u = edges[p[u]].from;
        }
        return true;
    }
 
    int MincotMaxflow(int s, int t) {
        int flow = 0, cost = 0;
        while (SPFA(s, t, flow, cost));
        return cost;
    }
};
 
int main()
{
    int n, a, b;
    int cas = 0;
    while(scanf("%d%d%d", &n, &a, &b) != EOF) {
        if (n == 0) break;
        cas ++;
        MCMF sol;
        int s = 0;
        int t = 2 * n + 1;
        char x[N];
        C = 0; 
        memset(map, 0, sizeof(map));
        for (int i = 0; i < n; i++) {
            scanf("%s", x);
            for (int j = 0; j < n; j++) {
                if (x[j] == '.') map[i + 1][j + 1] = 1;
                else if (x[j] == 'C') {
                    map[i + 1][j + 1] = 2;
                    C++;
                }
            }
        }
        for (int limit = n; limit >= 0; limit--) {
            sol.init(t + 1);
            for (int i = 1; i <= n; i++) {
                sol.addedge(s, i, limit, 0);
                sol.addedge(i + n, t, limit, 0);
                sol.addedge(i, i + n, INF, 0);
                for (int j = 1; j <= n; j++) {
                    if (map[i][j] == 1) sol.addedge(i, j + n, 1, 1);
                    if (map[i][j] == 2) sol.addedge(i, j + n, 1, F + 1);
                }
            }
            int f = sol.MincotMaxflow(s, t);
            if (f / F < C) {
                printf("Case %d: impossible\n", cas);
                break;
            }
            f %= F;
            if (f * a < b * limit) continue;
            else {
                printf("Case %d: %d\n", cas, f - C);
                break;
            }
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值