博客园同步
原题链接
简要题意:
给定两个点集与一些边(保证每条边的顶点属于不同点集),求二分图最大匹配。
解释下:二分图最大匹配 就是 最大的边集使得每两条边都不相交,不共点 的边的个数。
那么你会说,我用并查集维护一下就行了?远远不是这样。
本题作为 匈牙利算法 的模板题讲解。
以下面这个关系为例:
假设有 wxq
,wsq
,zmx
,NBL
,bfw
,gyx
与 kkk
六个人。
其中,wxq
,zmx
,gyx
,bfw
是第一点集,其余是第二点集。
并且他们说:
wxq: 我觉得 wsq
,NBL
,kkk
都可以成为我的 CP \text{CP} CP。
zmx: 我觉得只有 wsq
可以成为我的 CP \text{CP} CP.
gyx: 我觉得 wsq
和 NBL
都可以食用。
bfw: 我觉得她们三个都不错啊。
好,下面我们要 尽量满足他们的需求。
第一个人 wxq
,好,他的第一个备选人是 wsq
,那么就先让他和 wsq
成为 CP \text{CP} CP.
第二个人 zmx
来了,并对 wxq
说:
你拿走了 wsq
那我怎么办???
wxq
想:行啊,反正我还有 NBL
和 kkk
.
于是,wxq被绿了 zmx
成功地 ~绿了wxq 得到了 wsq
,然后 wxq
与 NBL
成为 CP \text{CP} CP.
然后,第三个人 gyx
来了,并对 zmx
说:
你拿走了 wsq
那我怎么办?
zmx
想了想说:
我只有她一个备选人啊,我要生存!单身狗还是你做吧
于是 gyx 绿人计划失败
但是 gyx
看自己的备选人还有 NBL
,所以对 wxq
说:
喂喂喂,快点还我 NBL
.
wxq
想:没关系,反正我还有 kkk
对吧。
所以 wxq又被绿了 gyx
绿了wxq 与 NBL
成为了 CP \text{CP} CP,然后 wxq
被轮番绿 与 kkk
成为了 CP \text{CP} CP.
最后一个人 bfw
来了,他先找到 zmx
说:
wsq
给我行不?
zmx
说:我只有她一个人啦!对方拒绝了该请求。
bfw
又辗转来到 wxq
说:
kkk
给我行不?
wxq
说:你们都想绿我是吧,没门 我也只有她一个人了。
bfw
最终对 gyx
说:
救救我,我马上要单身了。
gyx
一口回绝道:我刚刚绿了 wxq,你就像反绿我是吧 我也只有 NBL
一人了,你再找找别人吧。
最后,bfw
成为了单身狗 被抛弃了,他没有 CP \text{CP} CP,所以上述的 二分图最大匹配 为 3 3 3.(满足了 3 3 3 个人的需求)
所以可见,匈牙利算法是一种基于 绿与被绿 协商与匹配的过程。
简要步骤如下:
如果后来的人 想绿别人 与前面的人发生冲突,前面的人会进行抉择进行第 2 2 2 步;否则后来的人就有了 CP \text{CP} CP. 有了,下一个!
如果前面的人发现 自己被绿了,还有退路或者能绿别人 自己没有 CP \text{CP} CP 可处,那么就拒绝;然后后来的人回到第 1 1 1 步,进行下一步 准备绿人 。
如果新来的那人被所有人拒绝了 绿人失败,则它就单着吧。 单身狗它不香吗
众所周知,只有最后绿人成功的人才能获得最终胜利!
有机会上,没机会创造机会也得上!
这就是 匈牙利算法 的大致内容。
你会发现,可能会出现 很多人迭代地绿对方 很多 协商与匹配的过程。 就比方说一个完全图(只有被绿了多次的人才能找到真正的CP,而且先下手为强),所以我们要用 dfs \text{dfs} dfs 进行递归搜索。
有的时候自己会 间接地绿自己 与自己协商。比方说:
1 1
1 2
2 1
3 1
3 2
1 1 1 和 2 2 2 绿 协商后, 1 → 2 1 \rightarrow 2 1→2, 2 → 1 2 \rightarrow 1 2→1,然后 3 3 3 来了。他 想绿1 先与 1 1 1 协商,然后 1 1 1 与 2 2 2 协商(循环被绿),然后 2 2 2 又和 1 1 1 协商, 1 1 1 又与 2 2 2 协商。。最后3成单身狗,他俩绿疯了 然后就没了。 3:喂你们没人选我么
所以,要用 v i s i vis_i visi 表示 当前 想绿人的 与别人协商的是几号,防止循环 绿 协商。
时间复杂度: O ( max ( n , m ) × e ) O(\max(n,m) \times e) O(max(n,m)×e)
实际得分: 100 p t s 100pts 100pts.
#pragma GCC optimize(2)
#include
using namespace std;
const int N=2e3+1;
inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}
int n,m,T,vis[N],mat[N];
//mat[i] 表示 i 的 CP 编号
vector<int>G[N]; //图
inline bool dfs(int dep,int bs) { //表示当前让 CP 是否成功
if(vis[dep]==bs) return 0;
vis[dep]=bs;
for(int i=0;i<G[dep].size();i++) {
int x=G[dep][i];
if(!mat[x] || dfs(mat[x],bs)) {
//没有匹配 或者 可以让 CP 就换 CP
mat[x]=dep; //记录匹配
return 1;
}
} return 0;
}
int main(){
n=read(),m=read(),T=read();
while(T--) {
int x=read(),y=read();
G[x].push_back(y);
} int ans=0;
for(int i=1;i<=n;i++)
if(dfs(i,i)) ++ans;
printf("%d\n",ans);
return 0;
}