圆桌问题【二分图多重匹配】网络流24题

时间:2020-11-28 06:10:59

? 问题描述:
假设有来自 n 个不同单位的代表参加一次国际会议。每个单位的代表数分别为
n i r i , , 2 , 1 ,  = 。会议餐厅共有 m 张餐桌,每张餐桌可容纳 ) , , 2 , 1 ( m i c i  = 个代表就餐。
为了使代表们充分交流, 希望从同一个单位来的代表不在同一个餐桌就餐。 试设计一个算法,
给出满足要求的代表就餐方案。
? 编程任务:
对于给定的代表数和餐桌数以及餐桌容量,编程计算满足要求的代表就餐方案。
? 数据输入:
由文件 input.txt 提供输入数据。文件第 1 行有 2 个正整数 m和 n,m 表示单位数,n 表
示餐桌数,1<=m<=150, 1<=n<=270。文件第 2 行有 m 个正整数,分别表示每个单位的代表
数。文件第 3 行有 n 个正整数,分别表示每个餐桌的容量。
? 结果输出:
程序运行结束时,将代表就餐方案输出到文件 output.txt 中。如果问题有解,在文件第
1 行输出 1,否则输出 0。接下来的 m行给出每个单位代表的就餐桌号。如果有多个满足要
求的方案,只要输出 1 个方案。
输入文件示例  输出文件示例
input.txt  output.txt
4 5
4 5 3 5
3 5 2 6 4
1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5

今天才发现我一直用的网络流模板不是最终版本,说实话真心看不下去黄大神那种代码风格==

一看这个题的分类是二分图,马上就想拆点是不对的,图样图森破的觉得应该每个人拆成两个点,两边连源点汇点的时候加流量限制为桌子人数,中间不是一个单位的就连线orz这也说不过去啊,压根没有道理可言。而正确的二分图多重匹配确实要在源点汇点连边的时候加限制,但是也不是你这么加的啊,(⊙﹏⊙)b。明明就应该单位在左侧,桌子在右侧,左侧连源点时限制为单位人数,右侧连汇点时限制为桌子人数,每个单位可以向各个桌子连一条流量为1的边。没了。还是很好理解的

【问题分析】


二分图多重匹配问题,可以用最大流解决。


【建模方法】


建立二分图,每个单位为X集合中的顶点,每个餐桌为Y集合中的顶点,增设附加源S和汇T。


1、从S向每个Xi顶点连接一条容量为该单位人数的有向边。
2、从每个Yi顶点向T连接一条容量为该餐桌容量的有向边。
3、X集合中每个顶点向Y集合中每个顶点连接一条容量为1的有向边。


求网络最大流,如果最大流量等于所有单位人数之和,则存在解,否则无解。对于每个单位,从X集合对应点出发的所有满流边指向的Y集合的顶点就是该单位人员的安排情况(一个可行解)。


【建模分析】


对于一个二分图,每个顶点可以有多个匹配顶点,称这类问题为二分图多重匹配问题。X,Y集合之间的边容量全部是1,保证两个点只能匹配一次(一个餐桌上只能有一个单位的一个人),源汇的连边限制了每个点匹配的个数。求出网络最大流,如果流量等于X集合所有点与S边容量之和,那么则说明X集合每个点都有完备的多重匹配。


【问题另解】


贪心,更好的方法其实是贪心。首先把所有单位和餐桌按人数从大到小排序,一种适当的贪心策略就是对于每个单位,所有人每次尽量去剩余容量较大的餐桌就坐。按照这种贪心策略,如果某时发现有人已经无法就坐,则无解。具体方法为用线段树维护餐桌的剩余容量,按人数从多到少安排每个单位的人员,每次安排就是把容量餐桌前k大的餐桌人数减1(k为该单位人数)。为保证线段树前k位时刻为前k大,要维护第k与第k+1,k+2,...人数与第k相等的位置,减少第k大时要减少尽量靠后的,这样才能保证单调。

#include<cstdio>
using namespace std;
const int mm=555555;
const int mn=555;
const int oo=1000000000;
int node,src,dest,edge;
int reach[mm],flow[mm],next[mm];
int head[mn],work[mn],dis[mn],q[mn];
inline int min(int a,int b)
{
    return a<b?a:b;
}
inline void prepare(int _node,int _src,int _dest)
{
    node=_node,src=_src,dest=_dest;
    for(int i=0;i<node;++i)head[i]=-1;
    edge=0;
}
inline void addedge(int u,int v,int c)
{
    reach[edge]=v,flow[edge]=c,next[edge]=head[u],head[u]=edge++;
    reach[edge]=u,flow[edge]=0,next[edge]=head[v],head[v]=edge++;
}
bool Dinic_bfs()
{
    int i,u,v,l,r=0;
    for(i=0;i<node;++i)dis[i]=-1;
    dis[q[r++]=src]=0;
    for(l=0;l<r;++l)
        for(i=head[u=q[l]];i>=0;i=next[i])
            if(flow[i]&&dis[v=reach[i]]<0)
            {
                dis[q[r++]=v]=dis[u]+1;
                if(v==dest)return 1;
            }
    return 0;
}
int Dinic_dfs(int u,int exp)
{
    if(u==dest)return exp;
    for(int &i=work[u],v,tmp;i>=0;i=next[i])
        if(flow[i]&&dis[v=reach[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0)
        {
            flow[i]-=tmp;
            flow[i^1]+=tmp;
            return tmp;
        }
    return 0;
}
int Dinic_flow()
{
    int i,ret=0,delta;
    while(Dinic_bfs())
    {
        for(i=0;i<node;++i)work[i]=head[i];
        while((delta=Dinic_dfs(src,oo)))ret+=delta;
    }
    return ret;
}
int main()
{
    freopen("cin.txt","r",stdin);
   // freopen("output.txt","w",stdout);
    int m,n,u,v,c,sum;
    while(scanf("%d%d",&m,&n)&&n)
    {
        prepare(m+n+2,0,m+n+1);
        sum=0;
        for(u=1;u<=m;++u)scanf("%d",&c),addedge(src,u,c),sum+=c;
        for(v=1;v<=n;++v)scanf("%d",&c),addedge(v+m,dest,c);
        for(u=1;u<=m;++u)
            for(v=n;v>0;--v)addedge(u,v+m,1);
        if(Dinic_flow()==sum)
        {
            printf("1\n");
            for(u=1;u<=m;printf("\n"),++u)
                for(c=head[u];c>=0;c=next[c])
                    if(flow[c^1])printf("%d ",reach[c]-m);
        }
        else printf("0\n");
    }
}