一、二分图匹配
定义:
1、给定一个二分图G,在G的一个子图M中,M的边集{E}中的任何一条边都不依附于同一个节点。
(实际上也可以理解为,集合内没有点相连,集合间有点相连,且每个点只有一条边可以伸出。)
2、匹配边:被选择的边
3、增广路:一个未匹配边开始,交替匹配—未匹配—匹配。。。到另一条未匹配边结束的路。每次选择一条增广路将其取反可以将匹配数+1.
4、匹配:可以做动词,选择一条边将两个点匹配到一起。也可以作名词,表示一个匹配边集。
5、最大匹配:指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数。最大匹配是所有极大匹配当中边数最大的一个匹配。选择这样的边数最大的子集称为图的最大匹配问题。
问题:求二分图的最大匹配
方法:匈牙利算法
算法思路:对于每一个点,我们把它加入匹配,然后寻找增广路,找到一条完整的增广路,将整条增广路取反,使匹配数加一。
模板:
bool match(int u){
rep(i,1,n){
if(v[i]||!m[u][i])continue;
v[i]=1;
if(!matching[i]||match(matching[i])){
matching[i]=u;
return 1;
}
}
return 0;
}
void hungarian(){
rep(i,1,l){
memset(v,0,sizeof(v));
if(match(i))ans++;
}
return;
}
最小路径覆盖:
定义:在一个有向图当中,选取一些的路径,可以覆盖所有的点,并且每个点只和一条路径相关联。
可以理解为,在每条路径中,从起点走到终点,可以将图中的每一个点都经过一遍而不重复。
建图方式:每个点拆成两个点,a1,a2,如果有一条a-b的有向边,在新图中连一条a1-b2的边。
最小路径覆盖=点数-新图的最大匹配数。
证明:在新图中的每一条匹配可以理解为两个点可以建立一条路径,即路径数-1.
最小点覆盖:
定义:二分图中,选取最少的点数,使这些点和所有的边都有关联(把所有的边的覆盖),叫做最小点覆盖。
最小点覆盖=最大匹配数
证明:
反证法,如果在当前匹配中存在一条边没有被覆盖,说明它的两个顶点都没有被选取,即存在一条可行的匹配边,将其匹配即可。在这种情况下,这个匹配也就不是最大匹配,一直进行下去,就会变成最大匹配,同时所有边也会被覆盖。
最大独立点集:
定义:在一个二分图中,选择一些顶点,使得所选择的点集中任意两个顶点之间没有边相连
最大独立点集=点数-最大匹配数。
(实际上是点数-最小顶点覆盖数。)
证明:
反证法,因为是最小顶点覆盖,所以这时必定所有的边都至少有一个点被选中了。也就意味着不会再有其他的点互相连接。同时因为这是最小顶点覆盖,所以这是最大独立点集。
例题:
1、春天来了
。。。贪心。。。
2、bzoj2744 朋友圈
首先a国的条件表示了你最多在他们国家只能选两个人,一奇一偶。
B国的话,偶数可以当朋友,奇数可以当朋友,有条件的奇偶可以当朋友。这个图的补图是一个二分图,其中的边表示不可以当朋友的人。那么它去除掉最小点覆盖后的子图就是所有可以互相当朋友的人。
那么我们枚举a国的选择情况,然后对于b国建图,跑匈牙利算法
代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define maxn 250
#define maxm 3100
int A[maxn],B[maxm];
int len,lem,bf[maxm];
int ask[maxm],tim;//用时间戳
int ln[maxm],lm[maxm];
bool bk[maxm][maxm],bo[maxn][maxm];
//bk[i][j]存B国中i,j是否突破了奇偶限制而成为了朋友
int mymax(int x,int y){return (x>y)?x:y;}
bool ffind(int x)
{
int i;
for (i=1;i<=lem;i++)
if (ask[i]!=tim && !bk[ln[x]][lm[i]])
//如果成为了朋友 那么补图中他们两个是不能连边的
{
ask[i]=tim;
if (bf[i]==-1 || ffind(bf[i]))
{
bf[i]=x;
return true;
}
}
return false;
}
bool count(int x)
{
int ret=0;
while (x)
{
if (x&1) ret++;
x>>=1;
}return ret&1;
}
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
int n,m,r,i,j,k,x,y,ia,num,ans;
scanf("%d%d%d",&n,&m,&r);
for (i=1;i<=n;i++)
scanf("%d",&A[i]);
for (i=1;i<=m;i++)
scanf("%d",&B[i]);
memset(bo,false,sizeof(bo));
memset(bk,false,sizeof(bk));
for (i=1;i<=r;i++)
{
scanf("%d%d",&x,&y);
bo[x][y]=true;
}
for (i=1;i<m;i++)
for (j=i+1;j<=m;j++)
if ((B[i]+B[j])&1)
{
if (count(B[i]|B[j])) bk[i][j]=bk[j][i]=true;
}
memset(ask,0,sizeof(ask));
ans=tim=0;
len=lem=num=0;
for (i=1;i<=m;i++)
if (B[i]&1) ln[++len]=i;
else lm[++lem]=i;
memset(bf,-1,sizeof(bf));
for (i=1;i<=len;i++)
{
tim++;
if (ffind(i)) num++;
}
ans=mymax(ans,len+lem-num);
for (i=1;i<=n;i++)
{
len=lem=num=0;
for (j=1;j<=m;j++)
if (bo[i][j])
{
if (B[j]&1) ln[++len]=j;
else lm[++lem]=j;
}
memset(bf,-1,sizeof(bf));
for (j=1;j<=len;j++)
{
tim++;
if (ffind(j)) num++;
}
ans=mymax(ans,1+len+lem-num);
}
for (i=1;i<n;i++)
for (j=i+1;j<=n;j++) if ((A[i]+A[j])&1)
{
len=lem=num=0;
for (k=1;k<=m;k++)
if (bo[i][k] && bo[j][k])
{
if (B[k]&1) ln[++len]=k;
else lm[++lem]=k;
}
memset(bf,-1,sizeof(bf));
for (k=1;k<=len;k++)
{
tim++;
if (ffind(k)) num++;
}
ans=mymax(ans,2+len+lem-num);
}
printf("%d\n",ans);
return 0;
}
cdoj1432
相邻的格子实际上可以理解为不可以同时被选择。
因为你摆了一个箱子,旁边那个格子也就被占住了,没有意义了。
于是连边,二分图(黑白棋盘染色)
#include<bits/stdc++.h>
#define MAXN 10005
using namespace std;
char s[105][105];
vector<int> g[MAXN];
int from[MAXN],tot;
bool use[MAXN];
int N;
bool match(int x) {
for(int i=0; i<g[x].size(); i++) {
if(!use[g[x][i]]) {
use[g[x][i]]=true;
if(from[g[x][i]]==-1||match(from[g[x][i]])) {
from[g[x][i]]=x;
return true;
}
}
}
return false;
}
int hungary() {
tot=0;
memset(from,-1,sizeof(from));
for(int i=0; i<N; i++) {
memset(use,0,sizeof(use));
if(match(i))
tot++;
}
return tot;
}
void init() {
for(int i=0; i<N; i++)
g[i].clear();
}
int main() {
int n,m;
while(scanf("%d%d",&n,&m)!=EOF) {
N=n*m;
init();
for(int i=0; i<n; i++)
scanf("%s",s[i]);
for(int i=0; i<n; i++) {
for(int j=0; j<m-1; j++) {
if(s[i][j]=='.'&&s[i][j+1]=='.') {
int u=i*m+j;
int v=u+1;
g[u].push_back(v);
g[v].push_back(u);
}
}
}
for(int i=0; i<m; i++) {
for(int j=0; j<n-1; j++) {
if(s[j][i]=='.'&&s[j+1][i]=='.') {
int u=j*m+i;
int v=(j+1)*m+i;
g[u].push_back(v);
g[v].push_back(u);
}
}
}
int ans=hungary();
printf("%d\n",ans/2);
}
return 0;
}