题意:
有n个学生,有m对人是认识的,每一对认识的人能分到一间房,问能否把n个学生分成两部分,每部分内的学生互不认识,而两部分之间的学生认识。如果可以分成两部分,就算出房间最多需要多少间,否则就输出No。
分析:
首先,能否分为两部分,使,每部分内的学生互不相识。即判断是否是二分图,使用染色法。然后,学生中每一对互相认识的学生可以分配到一个房间,求最大的房间数,就是求二分图匹配的最大数量。
判断是否为二分图:
无向图G为二分图的充分必要条件是,G至少用两个顶点,且其所有的回路的长度均为偶数。即判断,是否存在奇圈。方法:染色判断法:任选某个起点为染色起点,同时把相邻有边的点染色为对立的点,通过bfs或者dfs完成染色,若走到某一步发现当前点和相邻的点颜色相同,说明出现了奇圈,矛盾。
匈牙利算法求最大匹配数:
原理 通过dfs或者bfs进行遍历,遇到新的未匹配的点或者有增广路径(若有增广路径就可以反转,则最大匹配数就可以加1)返回值为1则最大匹配数+1,然后继续遍历,直到完成遍历或者已经找到最大匹配数。
匈牙利算法的正确性在于贪心策略(hungry),重要特点:当一个节点成为匹配点的时候,至多因为找到增广路而更换匹配对象,而不会再变回非匹配点
一题一式:一般的二分模板题的话套路是给你两列数,不同数组之间有关系,每个组之间没有联系,求最大匹配。本题中,给定一个母图,要求再母图中进行分割,分成两部分,所有的点满足形式上的二分图。(注意是两列,每一列数都包含了所有的数,然后进行匹配,问题核心代码:linker[x]=i,linker[i]=x)
代码:
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=222;
const int Maxn=1000006;
int e[maxn][maxn];
int tag[maxn];
int linker[maxn];
int used[maxn];
int n,m;
int bfs(int st){
memset(tag,-1,sizeof(tag));
queueq;
q.push (st);
tag[st]=1;
while(!q.empty()){
int temp=q.front ();
q.pop();
for(int i=1;i<=n;i++){
if(e[temp][i]){
if(tag[i]==-1){
tag[i]=(tag[temp]+1)%2;
q.push (i);
}else if(tag[i]==tag[temp]){
return 1;
}
}
}
}
return 0;
}
int dfs(int u){
for(int i=1;i<=n;i++){
if(e[u][i]&&!used[i]){
used[i]=1;
if(linker[i]==-1||dfs(linker[i])){
linker[u]=i;linker[i]=u;//***自最大二分图匹配核心
return 1;
}
}
}
return 0;
}
int hungry(void){
int res=0;
memset(linker,-1,sizeof(linker));
for(int u=1;u<=n;u++){
if(linker[u]!=-1)continue;
memset(used,0,sizeof(used));
if(dfs(u)){
res++;
}
}
return res;
}
int main(){
while(~scanf("%d%d",&n,&m)){
memset(tag,-1,sizeof(tag));
memset(e,0,sizeof(e));
while(m--){
int x,y;
scanf("%d%d",&x,&y);
e[x][y]=e[y][x]=1;
}
if(bfs(1)){
printf("No\n");
}else{
printf("%d\n",hungry());
}
}
}