题解:UVA1104 芯片难题 Chips Challenge

· · 题解

思路:

首先是由于总数在变,所以每列的最大值不好确定,考虑枚举 mx 表示每行/列的芯片最大值。

考虑建模:

每次判是否满流,然后最小费用表示最小不能放的芯片数量,相当于最大能放的数量。

Code:

#define ll long long
using namespace std;

const int Maxn = 100, Maxm = 3005, INF = INT_MAX/2-5;

namespace Josh_zmf {

    int N, A, B, left_x[Maxn], left_y[Maxn]; string s[Maxn];
    int pi[Maxn], fa[Maxn], fa_e[Maxn], bj[Maxn], now = 1;

    namespace EDGE {
        int sz = 1, head[Maxn];
        struct Edge { int next, to, flow, cost; } edge[Maxm<<1]; // flow: 剩余流量,cost: 单价
        inline void Add_edge(int u, int v, int w, int c) {
            // printf("Add_edge(%d, %d, %d, %d)\n", u, v, w, c);
            edge[++sz] = {head[u], v, w, c};
            head[u] = sz;
            edge[++sz] = {head[v], u, 0, -c};
            head[v] = sz;
        }
    } using namespace EDGE;

    inline void init(int u) { // 随便找一棵生成树
        bj[u] = 1;
        for(int i=head[u], v; i; i=edge[i].next) {
            v = edge[i].to;
            if(bj[v] or !edge[i].flow)  continue;
            fa[v] = u, fa_e[v] = i, init(v);
        }
    }

    inline int phi(int u) {
        if(bj[u] == now)    return pi[u];
        bj[u] = now, pi[u] = phi(fa[u]) + edge[fa_e[u]].cost;
        return pi[u];
    }

    inline int pushflow(int e) {
        static int stk[Maxn]; now++;
        for(int u=edge[e^1].to; u; u=fa[u]) bj[u] = now;
        int lca = edge[e].to;
        for(; bj[lca]!=now; lca=fa[lca])    bj[lca] = now;
        // 找到替换边(剩余流量最小的)
        int mn = edge[e].flow, top = 0, id = 0, type = 0;
        for(int u=edge[e^1].to; u!=lca; u=fa[u]) {
            stk[++top] = fa_e[u];
            if(edge[fa_e[u]].flow < mn) {
                mn = edge[fa_e[u]].flow, id = u, type = 1;
            }
        }
        for(int v=edge[e].to; v!=lca; v=fa[v]) {
            stk[++top] = fa_e[v]^1;
            if(edge[fa_e[v]^1].flow < mn) {
                mn = edge[fa_e[v]^1].flow, id = v, type = 2;
            }
        }
        stk[++top] = e;
        // 延环推送流量
        int cost = 0;
        for(int i=1; i<=top; i++) {
            cost += edge[stk[i]].cost*mn;
            edge[stk[i]].flow -= mn;
            edge[stk[i]^1].flow += mn;
        }
        if(!type)   return cost; // 最小的就是当前边,不用加入生成树
        // 加入树上,反转一条链的方向
        if(type == 2)   e ^= 1;
        int u = edge[e^1].to, v = edge[e].to;
        for(; v!=id; ) {
            e ^= 1, bj[u] = 0, swap(fa_e[u], e); // u 链上的信息过期
            int tmp = fa[u];
            fa[u] = v, v = u, u = tmp; // 反转方向
        }
        return cost;
    }

    inline pair<int, int> Simplex(int s, int t) {
        memset(fa_e, 0, sizeof(fa_e));
        Add_edge(t, s, INF, -INF), init(t), fa[t] = fa_e[t] = 0;
        bj[t] = now = 2; int flow = 0, cost = 0;
        while(1) {
            bool flag = 0;
            for(int i=2; i<=sz; i++) {
                if(!edge[i].flow)   continue;
                if(edge[i].cost+phi(edge[i^1].to)-phi(edge[i].to) >= 0) continue; // 没负环
                cost += pushflow(i), flag = 1;
            }
            if(!flag)   break;
        }
        flow = edge[sz].flow;
        return {flow, cost+flow*INF};
    }

    inline int main() {
        for(int Case=1; 1; Case++) {
            cin>> N>> A>> B;
            if(!N)  break;
            for(int i=1; i<=N; i++) left_x[i] = left_y[i] = 0;
            int Can = 0, Had = 0;
            for(int i=1; i<=N; i++) {
                cin>> s[i], s[i] = ' ' + s[i];
                for(int j=1; j<=N; j++) {
                    if(s[i][j] == '.')  Can++, left_x[i]++, left_y[j]++;
                    else if(s[i][j] == 'C') Had++, left_x[i]++, left_y[j]++;
                }
            }
            int S = 2*N+1, T = 2*N+2, ans = -1;
            // for(int limit=N; ~limit; limit--) {
            for(int limit=1ll*(Can+Had)*A/B; ~limit; limit--) {
                memset(head+1, 0, (2*N+2)<<2), sz = 1;
                for(int i=1; i<=N; i++) {
                    Add_edge(S, i, left_x[i], 0), Add_edge(i+N, T, left_y[i], 0);
                    Add_edge(i, i+N, limit, 0);
                }
                for(int i=1; i<=N; i++) {
                    for(int j=1; j<=N; j++) if(s[i][j] == '.') Add_edge(i, j+N, 1, 1);
                }
                auto res = Simplex(S, T);
                if(res.first == Can+Had and (Can+Had-res.second)*A/B>=limit)    ans = max(ans, Can-res.second);
                // if(res.first == Can+Had and (Can+Had-res.second)*A/B>=limit) printf("limit:%d, ans:%d, res:{%d, %d}\n", limit, ans, res.first, res.second);
                // puts("_______________________");
            }
            if(ans == -1)   cout<< "Case "<< Case<< ": impossible\n";
            else    cout<< "Case "<< Case<< ": "<< ans<< '\n';
        }
        return 0;
    }

}

int main() {
    Josh_zmf::main();
    return 0;
}