首页 > 代码库 > hiho 第117周 二分图多重匹配,网络流解决

hiho 第117周 二分图多重匹配,网络流解决

描述

学校的秋季运动会即将开始,为了决定参赛人员,各个班又开始忙碌起来。

小Hi和小Ho作为班上的班干部,统计分配比赛选手的重任也自然交到了他们手上。

已知小Hi和小Ho所在的班级一共有N名学生(包含小Hi和小Ho),编号依次为1..N。

运动会一共有M项不同的比赛,编号为1..M。第i项比赛每个班需要派出m[i]名选手参加。

根据小Hi和小Ho的统计,编号为i的学生表示最多同时参加a[i]项比赛,并且给出他所擅长的b[i]项比赛的编号。

小Hi和小Ho希望将每个学生都安排到他所擅长的比赛项目,以增加夺冠的可能性。同时又要考虑满足每项比赛对人数的要求,当然给一个学生安排的比赛项目也不能超过他愿意参加的比赛项目数量。

根据统计的结果,小Hi和小Ho想知道能否有一个合适的安排,同时满足这些条件。

提示:二分图多重匹配

输入

第1行:1个整数T,表示一共有T(2≤T≤5)组数据,每组数据按如下格式给出:

第1行:2个正整数N,M。1≤N≤100,1≤M≤100。

第2行:M个整数,第i个数表示第i个项目需要的选手数量m[i]。1≤m[i]≤N。

第3..N+2行:若干整数,第i+2行表示编号为i的学生的信息。先是a[i],b[i],接下来b[i]个整数,表示其所擅长的b[i]个项目。1≤a[i]≤M

输出

第1..T行:第i行表示第i组数据能否满足要求,若能够输出"Yes",否则输出"No"。

 

小Ho:真是麻烦啊,要不让他们自己报名项目怎么样?

小Hi:那样可能满足不了人数的要求啊,还是根据他们的意愿来进行分配吧。

小Ho:要是每个项目都只要派一个人参加就好了。这样只需要一个简单的二分图匹配就可以决定了!

小Hi:但是现在的情况是一个人可以参加多个项目,一个项目也需要多个人参加才行。

小Ho:对啊,所以这就问题所在啊。小Hi你有什么解决的办法么?

小Hi:嗯......有了!

小Ho:什么办法?

小Hi:还是从你刚刚说的二分图匹配入手,我们仍然将学生看作A部,比赛项目看作B部:

技术分享

接下来,根据每个学生的意愿我们将对应的A[i]和B[j]连起来。比如编号为i的学生擅长编号为j的项目,那么就连接A[i]-B[j]:

技术分享

小Ho:然后呢?

小Hi:这次的问题和二分匹配不同的地方在于:匹配结果中,A[i]可能和多个B[j]相连,如果有一个可以来记录A[i]连接数量的量呢?

小Ho:记录A[i]的连接数量么?

小Hi:对!因为A[i]最多只能和a[i]的点相连。所以我们需要一个方法来限制这个量在0~a[i]之间变动。

小Ho:这听上去有点像网络流里面的流量,只能在0和容量之间变动。

小Hi:没错!这里我们要用的就是网络流。

小Ho:那具体怎么做呢?

小Hi:我们需要对之前的二分图进行进一步扩展。首先虚拟一个源点s和汇点t:

技术分享

接下来我们根据a[i],在s和A[i]之间连接一条容量为a[i]的边。

技术分享

同时将原来A[i]-B[j]直接的边都改造为从A[i]到B[j]的容量为1的边。

技术分享

最后我们还要连接所有的B[j]-t。由于比赛项目B[j]最多只需要m[j]名选手参加,所以我们不妨限制B[j]-t的容量为m[j]。

技术分享

这就是我们最后的网络流图。那么小Ho,你想想在这个图上面做的最大流有什么含义?

小Ho:我想想啊......我知道了!

在完成最大流后,这个网络流图的流量情况直接对应了一个分配方案:

s-A[i]:这一类边的流量表示了A[i]参加的项目数量。

A[i]-B[j]:这一类边的流量表示了A[i]是否参加项目B[j],若参加则流量为1,否则流量为0。

B[j]-t:这一类边的流量表示了参加比赛项目B[j]的选手数量。

由于流网络会自动调整去满足最大流量,所以它会自动调整每个A[i]-B[j]的流量来使得B[j]-t尽可能大。

若每个项目都能够满足人数的话,网络流会自己调整为所有B[j]-t都满流的情况。

小Hi:就是这样,我们只需要最后判断一下每一条B[j]-t的边是否满流,就可以知道能否满足需求。同时还可以根据A[i]-B[j]的情况,计算出每个选手所参加的比赛项目。

小Ho:这个方法好厉害,都不需要自己手动去进行调整了。

小Hi:没错,这也就是网络流的神奇之处。对于这类允许多条边的匹配问题,也被称为二分图多重匹配问题。而网络流正是它的一个简单解决方法。

 

技术分享
#include <bits/stdc++.h>using namespace std;#define maxn 505#define INF 0x3f3f3f3fstruct Edge{    int from,to,cap,flow;};struct Dinic{    int n,m,s,t;    vector<Edge> edge;    vector<int> G[maxn];    bool vis[maxn];    int d[maxn];    int cur[maxn];    void init()    {        for(int i=0;i<maxn;i++)            G[i].clear();        edge.clear();        memset(d,0,sizeof(d));        memset(vis,0,sizeof(vis));        memset(cur,0,sizeof(cur));    }    void addEdge (int from,int to,int cap)    {        edge.push_back((Edge){from,to,cap,0});        edge.push_back((Edge){to,from,0,0});        m = edge.size();        G[from].push_back(m-2);        G[to].push_back(m-1);    }    bool BFS()    {        memset(vis,0,sizeof(vis));        queue<int> Q;        Q.push(s);        d[s] = 0;        vis[s] = 1;        while(!Q.empty())        {            int x = Q.front();            Q.pop();            for(int i=0; i<G[x].size(); i++)            {                Edge & e = edge[G[x][i]];                if(!vis[e.to]&&e.cap>e.flow)                {                    vis[e.to] = 1;                    d[e.to] = d[x] + 1;                    Q.push(e.to);                }            }        }        return vis[t];    }    long long DFS(int x,int a)    {        if(x==t||a==0) return a;        long long flow = 0,f;        for(int & i = cur[x]; i<G[x].size(); i++)        {            Edge & e = edge[G[x][i]];            if(d[x] + 1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0)            {                e.flow +=f;                edge[G[x][i]^1].flow -=f;                flow +=f;                a-=f;                if(a==0) break;            }        }        return flow;    }    int Maxflow (int s,int t) {        this->s = s;this->t = t;        int flow = 0;        while(BFS()) {            memset(cur,0,sizeof(cur));            flow+=DFS(s,INF);        }        return flow;    }    //求最小割S,T;    void new_BFS(int s,int n)    {        memset(vis,0,sizeof(vis));        d[s] = 0;        vis[s] = 1;        queue<int> Q;        Q.push(s);        while(!Q.empty())        {            int u = Q.front();            Q.pop();            for(int i=0;i<G[u].size();i++)            {                Edge & e = edge[G[u][i]];                if(!vis[e.to]&&e.cap>e.flow)                {                    vis[e.to] = 1;                    d[e.to] = d[u] + 1;                    Q.push(e.to);                }            }        }        int cnt = 0;        for(int i=1;i<=n;i++)        {            if(vis[i]) cnt++;        }        printf("%d\n",cnt);        for(int i=1;i<=n;i++)            if(vis[i]) printf("%d ",i);        puts("");    }}sol;int main(){    int t;    scanf("%d",&t);    while(t--)    {        sol.init();        int n,m;        long long sum = 0;        scanf("%d%d",&n,&m);        int s=0,t=n+m+1;        for(int i=1;i<=m;i++)        {            int cap;            scanf("%d",&cap);            sum+=cap;            sol.addEdge(n+i,t,cap);        }        for(int i=1;i<=n;i++)        {            int a,b;            scanf("%d%d",&a,&b);            sol.addEdge(0,i,a);            for(int j=0;j<b;j++)            {                int to;                scanf("%d",&to);                sol.addEdge(i,n+to,1);  ///容量为1            }        }        //cout<<sol.Maxflow(s,t)<<endl;        if(sol.Maxflow(s,t)!=sum) puts("No");        else puts("Yes");    }    return 0;}
View Code

之前做过二分图的多重匹配。用的匈牙利,将match加一维成为二维的。加一个cnt数组记录B块匹配了多少。

现在这里变得复杂了,就是每个人可以选多个。

网络流来了,不知道哪个AK大神说的,一切图论皆网络流。

怎么建图:

技术分享

S到A的容量是ca,B到t的容量是cb,A到B的连线的容量是1。

重点来了,是否B都可以匹配好,就是网络流的最大流是否等于cb的总和。

 

hiho 第117周 二分图多重匹配,网络流解决