1. 程式人生 > >Poj 2112 [最大流] [二分圖的多重匹配]

Poj 2112 [最大流] [二分圖的多重匹配]

      此題涉及的知識點比較多:最短路徑,二分查詢,二分圖的多重匹配,最大流問題。

該題有3中解法:(都必須先二分答案,然後再用一下的方法)

     1. 重新建圖,把多重匹配的點分裂成多個點來解二分圖的最大匹配

     2. 直接解多重匹配(修改二分圖的最大匹配演算法中的一維陣列為二維陣列)

     3. 轉化成最大流(新增起點s 和終點t ,重新建圖):s 到每個牛的邊權為1,每個擠奶器到 t 的邊權為 m.

三種方法我都寫了程式碼,提交的時間分別為:

     1. 219MS    2. 79MS    3. 110MS

我覺得方法2和方法1的原理是一樣的,用一個二維陣列 match[][] 來標記就相當於把可多匹配的點分裂成了多個單匹配的點,但上面的時間說明方法2的效率比方法1高多了,仔細想想,方法2確實是能夠節約很多不必要的遍歷,這裡節約了時間。

至於方法3,我看 discuss 裡面都說用最大流來解此題效率非常低,時間達到 1000+ MS,難道是我用了 Improved - SAP 演算法???

方法1程式碼:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

const int MAXK = 30 + 1;
const int MAXC = 200 + 1;
const int MAXM = 15 + 1;
const int INF  = 100000000;

using namespace std;

int  k, c, m;
int  map[MAXK+MAXC][MAXK+MAXC];
bool path[MAXC][MAXK*MAXM];
int  match[MAXK*MAXM];
bool vst[MAXK*MAXM];


/* 把每個擠奶器點分裂成 m 個點,選邊權 <=tmp 的邊建立二分圖 */
void buildGraph(int tmp)
{
    memset(path, false, sizeof(path));

    for (int i=1; i<=c; i++)
        for (int j=1; j<=k; j++)
            if (map[k+i][j] <= tmp)
            {
                for (int t=1; t<=m; t++)
                {
                    path[i][(j-1)*m+t] = true;
                }
            }
}

bool DFS(int i)
{
    for (int j=1; j<=k*m; j++)
    {
        if (path[i][j] && !vst[j])
        {
            vst[j] = true;
            if (match[j] == -1 || DFS(match[j]))
            {
                match[j] = i;
                return true;
            }
        }
    }
    return false;
}

/* 針對該題,做了小小的修改,全部匹配返回 true, 否則返回 false */
bool maxMatch()
{
    memset(match, -1, sizeof(match));
    for (int i=1; i<=c; i++)
    {
        memset(vst, false, sizeof(vst));
        if (!DFS(i))
            return false;
    }
    return true;
}

/*  二分答案,求二分圖最大匹配  */
void solve()
{
    int low = 1, high = 200*(k+c), mid;
    while (low < high)
    {
        mid = (low + high)/2;
        buildGraph(mid);
        maxMatch() == true ? high = mid : low = mid+1;
    }
    printf("%d\n", low);
}

void floyd()
{
    int i, j, h, t = k+c;
    for (h=1; h<=t; h++)
        for (i=1; i<=t; i++)
            for (j=1; j<=t; j++)
                if (map[i][j] > map[i][h]+map[h][j])
                    map[i][j] = map[i][h]+map[h][j];
}

int main()
{
    scanf("%d %d %d", &k, &c, &m);
    for (int i=1; i<=k+c; i++)
        for (int j=1; j<=k+c; j++)
        {
            scanf("%d", &map[i][j]);
            if (map[i][j] == 0)
                map[i][j] = INF;
        }
    floyd();
    solve();
}
方法2(相比於方法1中修改的程式碼):
bool path[MAXC][MAXK];
int  match[MAXK][MAXM];
bool vst[MAXK];


/* 選邊權 <=tmp 的邊建立二分圖 */
void buildGraph(int tmp)
{
    memset(path, false, sizeof(path));

    for (int i=1; i<=c; i++)
        for (int j=1; j<=k; j++)
            if (map[k+i][j] <= tmp)
            {
                path[i][j] = true;
            }
}

/* 多重匹配要修改該函式,match[]改為二維陣列 */
bool DFS(int i)
{
    for (int j=1; j<=k; j++)
    {
        if (path[i][j] && !vst[j])
        {
            vst[j] = true;
            if (match[j][0] < m)
            {
                match[j][++match[j][0]] = i;
                return true;
            }
            for (int t=1; t<=m; t++)
            {
                if (DFS(match[j][t]))
                {
                    match[j][t] = i;
                    return true;
                }
            }
        }
    }
    return false;
}

/* 針對該題,做了小小的修改,全部匹配返回 true, 否則返回 false */
bool maxMatch()
{
    memset(match, 0, sizeof(match));
    for (int i=1; i<=c; i++)
    {
        memset(vst, false, sizeof(vst));
        if (!DFS(i))
            return false;
    }
    return true;
}
方法3程式碼:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

const int MAXK = 30 + 1;
const int MAXC = 200 + 1;
const int MAXM = 15 + 1;
const int MAXV = 300 + 1;
const int MAXE = 20000 + 1;
const int INF  = 100000000;

using namespace std;

struct Edge
{
    int v, cap, flow;
    Edge *next;
    Edge *rev;
} edge[MAXE];

Edge *node[MAXV];
int   nE;

int k, c, m;
int map[MAXK+MAXC][MAXK+MAXC];

inline void addEdge(int u, int v, int w)
{
    edge[nE].v = v;
    edge[nE].cap = w;
    edge[nE].next = node[u];
    node[u] = &edge[nE++];
}

inline void addRevEdge(int u, int v, int w)
{
    addEdge(u, v, w);
    addEdge(v, u, 0);
    edge[nE-1].rev = &edge[nE-2];
    edge[nE-2].rev = &edge[nE-1];
}

int n;
int start, tail;  // 流網路的起點和終點
int dis[MAXV];    // 點i到終點的距離為 dis[i]
int num[MAXV];    // dis值為i的點的個數為 num[i]; 用於優化

/*  用 <=tmp 的邊建圖  */
void buildGraph(int tmp)
{
    memset(node, 0, sizeof(node));
    nE = 0;
    for (int i=1; i<=k; i++)
        for (int j=1; j<=c; j++)
        {
            if (map[k+j][i] <= tmp)
                addRevEdge(k+j, i, 1);  // 容量為1
        }

    start = k+c+1;  // 新增一個起點
    tail  = k+c+2;  // 新增一個終點
    n = k+c+2;
    for (int i=1; i<=c; i++)
        addRevEdge(start, k+i, 1);  // 起點到每個cow點的容量為1
    for (int i=1; i<=k; i++)
        addRevEdge(i, tail, m);     // 每個k點到終點的容量為m
}

void BFS()
{
    memset(dis, -1, sizeof(dis));
    memset(num, 0, sizeof(num));

    int Q[MAXV], p, q;
    Q[0] = tail; p = 0; q = 1;
    dis[tail] = 0;
    num[0] = 1;
    while(p < q)
	{
        int u = Q[p++];
        for (Edge* e = node[u]; e; e = e->next)
		{
            if (e->rev->cap > 0 && dis[e->v] == -1)
            {
                dis[e->v] = dis[u] + 1;
                num[dis[e->v]]++;
                Q[q++] = e->v;
            }
        }
    }
}

int maxFlow()
{
    int u, totFlow = 0;
    Edge *nextEg[MAXV], *preEg[MAXV];
    for (int i=1; i<=n; i++)
        nextEg[i] = node[i];
    u = start;
    while (dis[start] < n)
	{
        if (u == tail)    // find an augmenting path
		{
            int augFlow = INF;
            for (int i = start; i != tail; i = nextEg[i]->v)
                if (augFlow > nextEg[i]->cap)
                    augFlow = nextEg[i]->cap;
            for (int i = start; i != tail; i = nextEg[i]->v)
			{
                nextEg[i]->cap -= augFlow;
                nextEg[i]->rev->cap += augFlow;
                nextEg[i]->flow += augFlow;
                nextEg[i]->rev->flow -= augFlow;
            }
            totFlow += augFlow;
            u = start;
        }

        Edge *e;
        for (e = nextEg[u]; e; e = e->next)
            if (e->cap > 0 && dis[u] == dis[e->v]+1)
                break;
        if (e)    // find an admissible arc, then Advance
		{
            nextEg[u] = e;
            preEg[e->v] = e->rev;
            u = e->v;
        }
		else    // no admissible arc, then relabel this vertex
		{
            if (0 == (--num[dis[u]])) break;    // GAP cut, Important!
            nextEg[u] = node[u];
            int mindis = n;
            for (e = node[u]; e; e = e->next)
                if (e->cap > 0 && mindis > dis[e->v])
                    mindis = dis[e->v];
            dis[u] = mindis + 1;
            ++num[dis[u]];
            if (u != start)
                u = preEg[u]->v;
        }
    }
    return totFlow;
}

/*  二分答案,最大流求解(I-SAP)  */
void solve()
{
    int low = 1, high = 200*(k+c), mid;
    while (low < high)
    {
        mid = (low + high)/2;
        buildGraph(mid);
        BFS();
        maxFlow() == c ? high = mid : low = mid+1;
    }
    printf("%d\n", low);
}

void floyd()
{
    int i, j, h, t = k+c;
    for (h=1; h<=t; h++)
        for (i=1; i<=t; i++)
            for (j=1; j<=t; j++)
                if (map[i][j] > map[i][h]+map[h][j])
                    map[i][j] = map[i][h]+map[h][j];
}

int main()
{
    scanf("%d %d %d", &k, &c, &m);
    for (int i=1; i<=k+c; i++)
        for (int j=1; j<=k+c; j++)
        {
            scanf("%d", &map[i][j]);
            if (map[i][j] == 0)
                map[i][j] = INF;
        }
    floyd();
    solve();
}