Dining (网络流拆点+Dinic)

给出N头奶牛和F种食物,D种饮料,每一头牛都有自己喜欢的饮料和食物,每一头牛获得自己喜欢的饮料和食物各一份,这头牛就会高兴,一种饮料或一种食物只能被一头牛获得,问最多能让多少头牛高兴。

这个题拆点什么的就不说了,我也想不出,但是对Dinic 算法倒是比暑假的时候清晰多了。

Dinic 先是对原网络从源点到汇点BFS分层,只要能把汇点的层分出来,那么说明肯定有流量可以从源点流到汇点,然后就是从源点进行DFS,找到一条(或多条)到汇点增加最小的流量(大概就是这个意思),最大流加上这个答案。这一次DFS增加的反悔边是为了下一次BFS之后使用的。每BFS一次就DFS一次找增广路,知道BFS分不出来汇点所在的层数,那么就说明不存在增光路了,算法结束。 时间复杂度 O(V^2,E) 。

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1000 + 10;
const int inf = 0x3f3f3f3f;
int N, F, D, k, dep[maxn], head[maxn];
int food[maxn], drink[maxn];
int S, T;
struct Edge
{
    int before;
    int flow;
    int to;
} e[maxn << 1];
inline void add(int u, int v, int c)
{
    e[k].to = v;
    e[k].flow = c;
    e[k].before = head[u];
    head[u] = k++;
}
int BFS(int S, int T)
{
    queue q;
    memset(dep, -1, sizeof(dep));
    dep[S] = 0;
    q.push(S);
    while (!q.empty())
    {
        int u = q.front();
        q.pop();
        if (u == T)
            return true;
        for (int i = head[u]; i != -1; i = e[i].before)
        {
            int v = e[i].to;
            if (e[i].flow && dep[v] == -1)
            {
                dep[v] = dep[u] + 1;
                q.push(v);
            }
        }
    }
    if (dep[T] == -1)
        return false; // 没有找到增广路径
    return true;
}
int DFS(int u, int flow)
{
    if (u == T)
        return flow; // 到达汇点 直接返回
    int sum = 0;
    for (int i = head[u]; i != -1; i = e[i].before)
    {
        int v = e[i].to;
        if (e[i].flow && dep[v] == dep[u] + 1)
        {
            int f = DFS(v, min(flow - sum, e[i].flow)); // 从当前结点可能找到多条满足条件的增广路径
            e[i].flow -= f;                             // flow-sum 是当前结点还能往外流的流量
            e[i ^ 1].flow += f;
            sum += f;
            if (sum == flow) // 往外流不动了
                return sum;
        }
    }
    return sum;
}
int Dinic(int S, int T)
{
    int max_flow = 0;
    while (BFS(S, T))
    {
        max_flow += DFS(S, inf);
    }
    return max_flow;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
    freopen("in.txt", "r", stdin);
#endif
    memset(head, -1, sizeof head);
    scanf("%d %d %d", &N, &F, &D);
    int f, d;
    S = 0;                 // 超级源点
    T = 2 * N + F + D + 1; // 超级汇点
                           //	 食物             2n+1~2n+F
                           //	 拆点之后左边的牛 1 ~ n
                           //	 拆点之后右边的牛 n+1 ~ 2n
                           //	 饮料 		      2n+F+1 ~ 2n+F+D
    for (int i = 1; i <= F; i++)
    {
        add(S, 2 * N + i, 1); // 超级源点与食物
        add(2 * N + i, S, 0); // 反向边
    }
    for (int i = 1; i <= D; i++)
    {
        add(2 * N + F + i, T, 1); // 饮料与超级汇点
        add(T, 2 * N + F + i, 0); // 反向边
    }
    for (int i = 1; i <= N; i++)
    {
        add(i, i + N, 1); // 左边的牛与右边的牛
        add(i + N, i, 0); // 反向边
    }
    for (int i = 1; i <= N; i++)
    {
        scanf("%d %d", &f, &d);
        for (int j = 1; j <= f; j++)
        {
            scanf("%d", &food[j]);
            add(2 * N + food[j], i, 1);
            add(i, 2 * N + food[j], 0);
        }
        for (int j = 1; j <= d; j++)
        {
            scanf("%d", &drink[j]);
            add(N + i, 2 * N + F + drink[j], 1);
            add(2 * N + F + drink[j], N + i, 0);
        }
    }
    printf("%d", Dinic(S, T));
    return 0;
}

 

你可能感兴趣的:(图论)