二分图最大多重匹配(poj 2289,poj 1698)

    题目意思就不解释了,实际上是求二分图的最大多重匹配,这里有个比较好的定义和总结,可以看看:

http://www.cppblog.com/MatoNo1/archive/2011/03/26/142766.aspx

 

     个人理解,就是二分图中的两部分图X和Y,若要找出其中的匹配且使得结点数较多的那部分B中,每个结点均有相匹配的点在另外一部分图A中,此时A中必存在点其与多条匹配边相关联。此时可以匈牙利算法解决这种二分图多重匹配问题,即求出点与匹配边的最大关联数(或者是找出一个点,使得它在多重匹配中相关联的最多边数在一定上限值内最小),此时match数组是二维的,第二维是记录与第2个集合里的节点v第i次匹配成功的节点的标号,其余的和原始的匈牙利算法相同。

 

      以下是poj 2289的代码:

 

  1. #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1070;
  2. struct node
    {
     int v;
     int next;
    }edge[N*N];
    int head[N],num;
    int match[N/2][N],Num[N/2];
    bool vis[N];
    int n,m;
  3. void init()
    {
     int i;
     for(i=0;i<=n+m+1;i++)
      head[i]=-1;
     num=0;
    }
  4. void addege(int u,int v)
    {
     edge[num].v=v;
     edge[num].next=head[u];
     head[u]=num++;
    }
  5. bool find(int u,int mid)
    {
     int i,j;
     for(i=head[u];i!=-1;i=edge[i].next)
     {
      int v=edge[i].v;
      if(!vis[v])
      {
       vis[v]=true;
       if(Num[v]<mid)
       {
        match[v][++Num[v]]=u;
        return true;
       }
       for(j=1;j<=mid;j++)
       {
        if(find(match[v][j],mid))
        {
         match[v][j]=u;
         return true;
        }
       }
      }
     }
     return false;
    }
  6. int main()
    {
     freopen("in.txt","r",stdin);
     while(scanf("%d%d",&n,&m)==2)
     {
      if(!n && !m) break;
      init();
      int i,u,v=1;
      char s[20],ch;
      for(i=1;i<=n;i++)
      {
       scanf("%s",s);
       while(1)
       {
        scanf("%d",&v);
        ch=getchar();
        addege(i,v);
        if(ch=='/n') break;
       }
      }
      int low=1,high=n;
      int ans=0;
      while(low<=high)
      {
       int mid=(low+high)/2;
       memset(match,-1,sizeof(match));
       memset(Num,0,sizeof(Num));
       bool flag=true;
       for(i=1;i<=n;i++)
       {
        memset(vis,0,sizeof(vis));
        if(!find(i,mid))
        {
         flag=false;break;
        }
       }
       if(flag)
       {
        ans=mid;high=mid-1;
       }
       else
        low=mid+1;
      }
      printf("%d/n",ans);
     }
     return 0;
    }

     这里是poj 1698的解题报告。其实也是一个二分图的多重匹配,用最大流做的。以电影和工作日期(要把星期展开成天)为二分图构图,加上一个超级源点和超级汇点,电影和日期间的边的容量是inf,而源点到电影,汇点到日期的边的容量是1,进行一次最大流算法以后,检查所得最大流是否等于所有电影日期的总和,是则“yes”,否则就是“no"。

      以下是代码:

 

 

  1. #include<cstdio>
    #include<cstring>
    #include<iostream>
    using namespace std;
    const int M=40000;
    const int N=500;
    const int inf=1<<29;
  2. struct node
    {
     int v,f;
     int next;
    }edge[M];
    int head[N],num;
    int work[30],d,w;
    int s,t,flow,maxweek,NN;
    int n,m;
  3. void init()
    {
     int i;
     memset(head,-1,sizeof(head));
     num=flow=0;
     s=0;
     maxweek=-1;
    }
  4. void addege(int u,int v,int f)
    {
     edge[num].v=v;
     edge[num].f=f;
     edge[num].next=head[u];
     head[u]=num++;
     edge[num].v=u;
     edge[num].f=0;
     edge[num].next=head[v];
     head[v]=num++;
    }
  5. void sap()
    {
     int pre[N],cur[N],dis[N],gap[N];
     int aug=inf,u;
     bool flag;
     int i;
     for(i=0;i<=NN;i++)
     {
      dis[i]=gap[i]=0;
      cur[i]=head[i];
     }
     gap[s]=NN;
     u=pre[s]=s;
     while(dis[s]<NN)
     {
      flag=0;
      for(int &j=cur[u];j!=-1;j=edge[j].next)
      {
       int v=edge[j].v;
       if(edge[j].f>0 && dis[u]==dis[v]+1)
       {
        flag=1;
        if(edge[j].f<aug) aug=edge[j].f;
        pre[v]=u;
        u=v;
        if(u==t)
        {
         flow+=aug;
         while(u!=s)
         {
          u=pre[u];
          edge[cur[u]].f-=aug;
          edge[cur[u]^1].f+=aug;
         }
         aug=inf;
        }
        break;
       }
      }
      if(flag) continue;
      int mindis=NN;
      for(j=head[u];j!=-1;j=edge[j].next)
      {
       int v=edge[j].v;
       if(edge[j].f>0 && dis[v]<mindis)
       {
        mindis=dis[v];
        cur[u]=j;
       }
      }
      if((--gap[dis[u]])==0) break;
      gap[dis[u]=mindis+1]++;
      u=pre[u];
     }
    }
  6. int main()
    {
      int T;
     scanf("%d",&T);
     while(T--)
     {
      scanf("%d",&n);
      init();
      int i,j,k;
      int sum=0;
      for(i=1;i<=n;i++)
      {
       for(j=0;j<7;j++)
        scanf("%d",&work[j]);
       scanf("%d%d",&d,&w);
       sum+=d;
       if(w>maxweek) maxweek=w;
       addege(s,i,d);
       for(j=0;j<w;j++)
       {
        for(k=0;k<7;k++)
         if(work[k])
         {
          addege(i,j*7+n+k+1,inf);
         }
       }
      }
      t=n+maxweek*7+1;
      NN=t+1;
      for(i=0;i<maxweek;i++)
       for(j=1;j<=7;j++)
        addege(i*7+n+j,t,1);
      sap();
      if(flow==sum)
       printf("Yes/n");
      else
       printf("No/n");
     }
     return 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值