题意:给出编号范围为N的人,然后再给出编号a b 之间有认识的关系。给出m组这样的关系,然后让你分出两组
使得每组中每个人互相都不认识(认识关系没有间接性),如果能分组的话,就将两个认识人放到一个房间中,求最多需要的房间数
思路:以前做过并查集关于分组的题所以首先想用并查集来试一下(并查集分组是开两倍的数组)
但是自析想想并查集分的时候实际上是有间接关系的,同时也不能判断怎样分配房间
然后就学习了:二分图匹配
二分图匹配问题就是给出一个图,只要两个点之间有边,那么这两个点就不能同属一个集合,必须分在两边。
(1)二分图的判定:这道题就判定是否是一个二分图:
方法:染色法 :我们可以从某个点出发,然后对其连接的点染色,该点染为1,连接的点染为2.如果
与其相连接的点已经染为1了,则说明这个肯定不是一个二分图
(2):二分图最大匹配:在所给定的图中我们尽可能使两两认识的在一起即我们可以每次
固定两个人的关系,并拿出图。将剩下的继续匹配,找到“增广路径”
(增广路径:有A、B集合,增广路由A中一个点通向B中一个点,再由B中这个点通向A中一个点……交替进行)
完整代码:
#include<iostream> #include<vector> #include<cstring> using namespace std; const int maxn = 10010; int n,m;//顶点数 边数 vector<int> G[maxn]; int vis[maxn],match[maxn]; int color[maxn] ; //0 没染色 1 -1不同色 bool dfs(int u, int c){ color[u] = c; for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; //颜色相同不行 if(color[v] == c) return false; //v没有染色,递归染色判断这种情况是否成立 if(color[v] == 0 && !dfs(v,-c)) return false; } return true; } bool solve(){ for(int i=1; i<=n; i++){ if(color[i] == 0) if(!dfs(i,1)){ return false; } } return true; } //匈牙利算法:dfs找到增广路径 bool Find(int u) { for(int i=0; i<G[u].size(); i++) { int x = i; if(!vis[x] && G[u][x]) { vis[x] = 1; if(!match[x] || Find(match[x])) { match[x] = u; return true; } } } return false; } int main(){ int t; cin>>t; while(t--){ cin >> n >> m; memset(color, 0, sizeof(color)); for(int i=0; i<maxn; i++) G[i].clear(); for(int i = 0; i < m; i++) { int s, t; cin >> s >> t; G[s].push_back(t); G[t].push_back(s); // 如果有向图则无需这一句 } if(solve()){ memset(match,0,sizeof(match)); for(int i=1;i<=n;i++){ memset(vis,0,sizeof(vis)); if(Find(i)) ans++; } cout<<ans/2<<endl; } else cout<<"No"<<endl; } return 0; }
bfs染色:
bool bfs(int s){ color[s] = 1; queue<int>q; q.push(s); while(!q.empty()){ int u = q.front(); q.pop(); for(int v = 1; v <= n; v++){ //相邻且没有染色 if(G[u][v] && color[v] == 0){ q.push(v); color[v] = -color[u];//染上不同的颜色 } if(G[u][v] && color[u] == color[v]){ return false; } } } //所有结点被染色且相邻结点颜色不一样 return true; }