题目链接
http://poj.org/problem?id=1325
题目大意
k个工作(从0开始编号),a机器n个模式(模式从0开始编号),b机器有m个模式(模式从0开始编号);
每个工作都可以由机器a的一个模式或机器b的一个模式完成且只能由机器a的一个模式或机器b的一个模式完成;
按一定顺序安排完成工作的顺序,使得切换机器的次数最少,求最少的切换机器次数;
最开始机器a和机器b都处于0模式。
解题思路
建图:
将每个工作需要的两个种机器模式连边,那么每一条边就代表一种工作;左集合是一条边的一个端点,右集合是此边的另一个端点。
建好图了就比较好想了,要将所有工作都完成,就意味着要把所有的边全部覆盖;这就是二分图最小点覆盖问题,也就可以转化为二分图最大匹配数。
为什么这么建图?
说实话,我没想到,直接把题意理解错了。
我们可以这么试着理解:
对于一个工作,要么选a机器的模式,要么选b机器的模式,也就是说,对于一个工作而言,不可能既选a机器的模式完成的它,又选b机器的模式完成它,这样必然不是最小点覆盖。类似这种不能同时选的,我们自然想把不能同时选的放到两个不同的集合中,这就是为什么要将一个工作的a机器模式和b机器模式连边。
只要覆盖住了每条边就代表完成了全部的工作。
注意
注意“最开始机器a和机器b处于0模式”,
意味着可以通过a机器的0模式或者b机器的0模式完成的任务,可以在不切换模式的情况下完成,所以不能访问与a或b的0模式相连的边,因为不需要覆盖这条边了,这条边已经被覆盖过了。
至于“最小点覆盖=最大匹配数”的证明,读者自己百度吧,我感觉挺难的,不会。
看代码吧!
AC代码
#include<iostream> #include<cstring> #include<vector> using namespace std; const int N=110; const int M=1005; vector<int> e[N]; int n,m,k,cnt,vis[N],link[N],job,ma,mb; bool dfs(int x) { for(int i=0;i<e[x].size();i++) { int v=e[x][i]; if(vis[v] || v==0) continue;//必须不能访问b机器的0模式 vis[v]=1; if(link[v]==0 || dfs(link[v])) { link[v]=x; return 1; } } return 0; } int main() { while(~scanf("%d",&n)) { if(n==0) break; scanf("%d%d",&m,&k); memset(link,0,sizeof link); cnt=0; for(int i=0;i<n;i++) e[i].clear(); for(int i=1;i<=k;i++) { scanf("%d%d%d",&job,&ma,&mb); e[ma].push_back(mb);//你也可以在这里判断一下,在mb!=0的前提下push_back,这样你就不用在dfs中continue掉v==0的情况了 } for(int i=1;i<n;i++)//必须是从a机器的1模式开始 { memset(vis,0,sizeof vis); if(dfs(i)) cnt++; } cout<<cnt<<endl; } }
总结
我觉得二分图的匈牙利算法还是需要时间沉淀;
自己对二分图的理解不是很透彻导致不会建图,同时对匈牙利算法的理解也不够深刻,导致实现代码的时候会漏掉细节。