二分图的最大匹配(匈牙利算法)

给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m条边。

数据保证任意一条边的两个端点都不可能在同一部分中。

请你求出二分图的最大匹配数。

二分图的匹配:给定一个二分图 G,在 G 的一个子图 MM 中,MM 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。

二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

匈牙利算法O(nm)

代码:

//匈牙利算法O(nm)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=510,M=1e5+10;
int h[N],e[M],ne[M],idx;
int match[N];//表示右半部点的匹配
bool vis[N];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool find(int x)
{
    for(int i=h[x];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!vis[j])
        {
            vis[j]=true;
            if(!match[j]||find(match[j]))//如果j点未匹配或j的匹配点可以找到新的点匹配
            {
                match[j]=x;//j匹配x
                return true;
            }
        }
    }
    return false;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int n1,n2,m;
    cin>>n1>>n2>>m;
    memset(h,-1,sizeof h);
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    int res=0;
    for(int i=1;i<=n1;i++)
    {
        memset(vis,0,sizeof vis);
        if(find(i)) res++;
    }
    cout<<res;
    return 0;
}

373. 車的放置:

给定一个 N 行 M 列的棋盘,已知某些格子禁止放置。

问棋盘上最多能放多少个不能互相攻击的車。

車放在格子里,攻击范围与中国象棋的“車”一致。

输入格式

第一行包含三个整数 N,M,T,其中 T 表示禁止放置的格子的数量。

接下来 TT 行每行包含两个整数 x 和y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。

输出格式

输出一个整数,表示结果。

数据范围

1≤N,M≤200

思路:

考虑二分图

将放置棋子的横坐标当作左半图,纵左边当作右半图,車当作连接的边。由于二分图匹配的性质可保证匹配的点不在同行或同列。即可转换为求二分图的最大匹配

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=210,M=4e4+10;
int n,m,t;
int a[N][N],match[N]; //a[i][j]==0表示i,j之间可连边
bool vis[N];
bool find(int x)
{
   for(int i=1;i<=m;i++)
   {
       if(!vis[i]&&!a[x][i])  
       {
           vis[i]=true;
           if(match[i]==0||find(match[i]))
           {
               match[i]=x;
               return true;
           }
       }
   }
   return false;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cin>>n>>m>>t;
    while(t--)
    {
        int x,y;
        cin>>x>>y;
        a[x][y]=1;
    }
     int res=0;
     for(int i=1;i<=n;i++)
     {
         memset(vis,0,sizeof vis);
         if(find(i)) res++;
     }
    cout<<res;
}

372. 棋盘覆盖

给定一个 N 行 N 列的棋盘,已知某些格子禁止放置。

求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。

输入格式

第一行包含两个整数 N 和 t,其中 t 为禁止放置的格子的数量。

接下来 t行每行包含两个整数 x 和 y,表示位于第 x 行第 y 列的格子禁止放置,行列数从 1 开始。

思路:

和前一题类似,都是放置问题。

考虑如何将其分为二分图,将第一个点放在左半图,则其相邻的点将置与右半图。将整个图划分别的话,坐标之和为奇数的在左半图,否则在右半图。可以发现划分后每个匹配即为骨牌放置的位置,并且不会出现重叠的情况。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=110;
int m[N][N];
PII match[N][N];//存匹配的点对
bool vis[N][N];
int n,t;
int nx[]={1,0,-1,0},ny[]={0,1,0,-1};
bool find(int x,int y)
{
    for(int i=0;i<4;i++) //枚举相邻点
    {
        int xx=x+nx[i],yy=y+ny[i];
        if(!vis[xx][yy]&&!m[xx][yy]&&xx&&yy&&xx<=n&&yy<=n)
        {
            PII tt=match[xx][yy];
            vis[xx][yy]=true;
            if(!tt.first||find(tt.first,tt.second))
            {
                match[xx][yy]={x,y};
                return true;
            }
        }
    }
    return false;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cin>>n>>t;
    while(t--)
    {
        int a,b;
        cin>>a>>b;
        m[a][b]=1;
    }
    int res=0;
    for(int i=1;i<=n;i++)
     for(int j=1;j<=n;j++)
     {
         if((i+j)%2&&!m[i][j])  //枚举可放置奇数点
         {
             memset(vis,0,sizeof vis);
             if(find(i,j)) res++;
         }
     }
    cout<<res;
    return 0;
}

总结:

由两个例题观之,对于求放置数量问题,可将其转化为匹配问题,再考虑如何将其划分为二分图,最后用匈牙利算法求解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值