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();
}


你可能感兴趣的:(c,优化,算法,网络,SAP,Path)