今天离散课老师讲了二分图(其实好像上节课就讲了,我似乎翘课了嘤嘤嘤),
然后我就复习,总结一下:
首先是百度百科给出的概念:
二分图,又叫二部图,是图论中的一种特殊模型。
设:G=(v,e)是一个无向图,
如果顶点v可分割成两个互不相交的子集(A,B)
并且图中的每条边(i,j)所关联的两个顶点i,j分别属于这两个不同的顶点集
则称G为一个二分图。
定义就是:
顶点集V可分割为两个互不相交的子集,
并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,
两个子集内的顶点不相邻
无向图G是二分图的充要条件是:
G至少有两个顶点,且所有回路的长度均为偶数。
最大匹配:
给定一个二分图G,
在G的一个子图M中,M的边集中的任意两条边都不依附于同一个顶点,则称M是一个匹配
选择这样的边数最大的子集称为图的最大匹配问题。入过一个匹配中,途中的每个顶点都和图中某条边相关联,则称匹配为完全匹配(完备匹配)
二分图的最大匹配用最大流说着匈牙利算法。
二分图的性质:
二分图中,点覆盖数就是匹配数。
1.二分图的最大匹配数等于最小覆盖数,即求最少的点使得每条边都至少和其中的一个点相关联,很显然直接取最大匹配的一段节点即可。
2.二分图的独立数等于顶点数减去最大匹配数,很显然的把最大匹配两端的点都从顶点集中去掉这个时候剩余的点是独立集,这是|V|-2*|M|,同时必然可以从每条匹配边的两端取一个点加入独立集并且保护其独立集性质。
3. DAG的最小路劲覆盖,将每个点拆点后作最大匹配,结果为n-m,求具体路径的时候顺着匹配边走就可以,匹配边 i→j',j→k',k→l'....构成一条有向路径。
4.最大匹配数=左边匹配点+右边未匹配点。
因为在最大匹配集中的任意一条边,如果他的左边没标记,右边被标记了,那么我们可以找到一条新的增广路,所以每一条边都至少被一个点覆盖。
5.最小边覆盖=图中点的个数-最大匹配数=最大独立集。
二分图判定:
有两顶点集且每条边的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接。
无向图G为二分图的充要条件是,G至少有两个顶点,且其所有回路的长度均为偶数。
判断二分图的常见方法是染色法:
开始对任意一未染色顶点染色,之后判断其相邻的顶点中,若未染色则将其染上和相邻顶点不同的颜色,若已染色且颜色和相邻顶点的颜色相同则说明不是二分图,若颜色不同则继续判断,BFS和DFS都可以搞定这个问题。
易知:任何无回路的图均是二分图
算法:
1.求最大匹配的一种显而易见的算法是:
找出全部匹配,然后保留匹配数最多的。
但是这个算法的复杂度为边数的指数级函数,因此,需要寻求更高效的算法。
2. 最大流实现二分图匹配:
增广路的定义(增广轨||交错轨):
若P是图G中一条联通两个为匹配顶点的路径,
并且属M的边和不属M的边(即已匹配和待匹配的边)在P上交替出现,
则称P为相对于M 的一条增广路经。
由增广路定义可以推出下面三个结论
(1) P的路径长度必定为奇数,第一条边和最后一条边都不属于M
(2) P经过取反操作可以得到一个更大的匹配M
(3) M为G的最大匹配 当且仅当 不存在相对于M的增广路径
有了这三个结论,就可以尝试着用最大流来解决二分图的最大匹配问题了:
首先建图,v1-v2之间的最大流量是设置为1(根据要求可以设置不同的数)
然后建立源 S 连接第一个点集,
建立汇 T 连接第二个点集,此时每条边的流量限制应该也是1。
然后就是简单的网络流板子了。辣鸡(我)只会dinic,所以就用dinic来解决了 QAQ ...
直接上板子把,至于网络流不会的请自行谷歌||百度....
我用的是当前弧优化过的dinic,至于更好的优化算法....我现在还不会..
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
//#include<map>
//#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
// register
const int MAXN=2e3+10;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
struct node{
int v,w,cost,nxt;
node(int _v=0,int _w=0,int _nxt=0):
v(_v),w(_w),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN<<1],cur[MAXN<<1],ecnt;//节点,当前弧优化
int dis[MAXN];//深度
int mp[MAXN][MAXN];//两点之间存在联系
int n,m,s,t;
void intt()
{
clean(head,-1);
clean(cur,-1);
clean(mp,0);
ecnt=0;
}
void add(int u,int v,int w)
{
edge[ecnt]=node(v,w,head[u]);
head[u]=ecnt++;
edge[ecnt]=node(u,0,head[v]);
head[v]=ecnt++;
}
//-------------
bool bfs()
{
clean(dis,-1);
dis[s]=0;
queue<int> que;
que.push(s);
while(que.size())
{
int u=que.front();
que.pop();
if(u==t)//找出最短的一条路
return 1;
for(int i=head[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]==-1&&edge[i].w>0)
{//没有被找到过 && 可行边
dis[temp]=dis[u]+1;
que.push(temp);
}
}
}
return 0;
}
int dfs(int u,int low)
{
if(u==t||low==0)
return low;
int res=0;
for(int &i=cur[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]==dis[u]+1&&edge[i].w>0)
{//符合深度 &&可行边
int f=dfs(temp,min(low-res,edge[i].w));
//找到这条路后根据这条路上的最小值刷新残余流量
//刷新残留网络
edge[i].w-=f;
edge[i^1].w+=f;
res=res+f;
if(res==low)//满了
break;
}
}
return res;
}
void dinic()
{
int ans=0;
while(bfs())
{
for(int i=0;i<=t;++i)
cur[i]=head[i];
ans+=dfs(s,INF);
}
cout<<ans<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
//二分图G可分为两个点集V1,V2,很明显有s-v1-v2-t
//设 v1有n个点,v2有m个点 ,源s是0,汇t是n+m+1
cin>>n>>m;
intt();
s=0,t=m+n+1;
for(int i=1;i<=n;++i)
add(s,i,1);
for(int i=1;i<=m;++i)
add(i+n,t,1);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(mp[i][j])//两点之间有关系
add(i,n+j,1);
}
}
//建图完毕
dinic();
}
举例子HDU-2063,一个可以当板子的题目;
直接套进去,把数据修改一下就行了,核心代码一模一样啊!我就复制粘贴过去就A了
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
//#include<map>
//#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
// register
const int MAXN=2e3+10;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
struct node{
int v,w,cost,nxt;
node(int _v=0,int _w=0,int _nxt=0):
v(_v),w(_w),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN<<1],cur[MAXN<<1],ecnt;//节点,当前弧优化
int dis[MAXN];//深度
int mp[550][550];//两点之间存在联系
int n,m,s,t;
void intt()
{
clean(head,-1);
clean(cur,-1);
clean(mp,0);
ecnt=0;
}
void add(int u,int v,int w)
{
edge[ecnt]=node(v,w,head[u]);
head[u]=ecnt++;
edge[ecnt]=node(u,0,head[v]);
head[v]=ecnt++;
}
//-------------
bool bfs()
{
clean(dis,-1);
dis[s]=0;
queue<int> que;
que.push(s);
while(que.size())
{
int u=que.front();
que.pop();
if(u==t)//找出最短的一条路
return 1;
for(int i=head[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]==-1&&edge[i].w>0)
{//没有被找到过 && 可行边
dis[temp]=dis[u]+1;
que.push(temp);
}
}
}
return 0;
}
int dfs(int u,int low)
{
if(u==t||low==0)
return low;
int res=0;
for(int &i=cur[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(dis[temp]==dis[u]+1&&edge[i].w>0)
{//符合深度 &&可行边
int f=dfs(temp,min(low-res,edge[i].w));
//找到这条路后根据这条路上的最小值刷新残余流量
//刷新残留网络
edge[i].w-=f;
edge[i^1].w+=f;
res=res+f;
if(res==low)//满了
break;
}
}
return res;
}
void dinic()
{
int ans=0;
while(bfs())
{
for(int i=0;i<=t;++i)
cur[i]=head[i];
ans+=dfs(s,INF);
}
cout<<ans<<endl;
}
int main()
{
std::ios::sync_with_stdio(false);
//二分图G可分为两个点集V1,V2,很明显有s-v1-v2-t
//设 v1有n个点,v2有m个点 ,源s是0,汇t是n+m+1
int k;
while(cin>>k)
{
if(k==0)
break;
cin>>n>>m;
intt();
s=0,t=m+n+1;
int a,b;
for(int i=1;i<=k;++i)
{
cin>>a>>b;
mp[a][b]=1;
}
for(int i=1;i<=n;++i)
add(s,i,1);//s - 女生
for(int i=1;i<=m;++i)
add(i+n,t,1);//男生 - t
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
if(mp[i][j])
add(i,n+j,1);// 女生 - 男生
}
}
//建图完毕
dinic();
}
}
3.匈牙利算法实现二分图最大匹配
上体育课,等回来再写...