一、匈牙利算法
解决二分图中的最大匹配问题(很多时候都可以把题目转变成二分图,刚学网络流有点头大)。两个互不相交的子集V1 ,V2 寻找他们能匹配到的最大数量。(由于我菜鸡,仅仅给出几种模板,网上有很多博客讲的很好了)
1、邻接表实现(O( n^3)
模板秒切
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1001;
int n1,n2,k;
//n1,n2为二分图的顶点集,其中x∈n1,y∈n2
int map[N][N],vis[N],link[N];
//link记录n2中的点y在n1中所匹配的x点的编号
int find(int x)
{
int i;
for(i=1;i<=n2;i++)
{
if(map[x][i]&&!vis[i])//x->i有边,且节点i未被搜索
{
vis[i]=1;//标记节点已被搜索
//如果i不属于前一个匹配M或被i匹配到的节点可以寻找到增广路
if(link[i]==0||find(link[i]))
{
link[i]=x;//更新
return 1;//匹配成功
}
}
}
return 0;
}
int main()
{
int i,x,y,s=0;
while(~scanf("%d",&k)&&k)
{
s=0;
scanf("%d%d",&n1,&n2);
memset(map,0,sizeof(map));
memset(link,0,sizeof(link));
for(i=0;i<k;i++)
{
scanf("%d%d",&x,&y);
map[x][y]=1;
}
for(i=1;i<=n1;i++)
{
memset(vis,0,sizeof(vis)); //每次记得初始化
if(find(i))
s++;
}
printf("%d\n",s);
}
return 0;
}
2、邻接表实现(O(n*m))
一样的思路,不过是改成了邻接表,数据较大(直接记这个就好了)。
#include<stdio.h>
#include<string.h>
#include<string>
#include<algorithm>
#include<queue>
#define Max 500
#define inf 0x3f3f3f3f
using namespace std;
void show(int *a,int n);
struct Node{
int next;
int to;
}edge[Max*Max];
int num_edge;
int head[Max];
void add_edge(int x,int y) //邻接表
{
edge[++num_edge].next=head[x];
edge[num_edge].to=y;
head[x]=num_edge;
}
bool vis[Max];//是否匹配过了
int link[Max];
bool find(int num)
{
int i,u;
for(i=head[num];i;i=edge[i].next) //邻接表访问
{
u=edge[i].to;
if(!vis[u])
{
vis[u]=1;
if(!link[u]||find(link[u]))
{
link[u]=num;
return true;
}
}
}
return false;
}
int main()
{
int t,n,cnt1,cnt2,x;
scanf("%d",&t);
while(t--)
{
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
memset(link,0,sizeof(link));
num_edge=0;
cnt1=0;
cnt2=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&x);
if(x)
{
cnt1++;
add_edge(i,j+n); //行
}
}
}
if(cnt1<n)
{
printf("No\n");
continue;
}
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(find(i))
cnt2++;
}
if(cnt2>=n)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}
3、需要自己根据题目构建一个二分图
简单练习:
AC代码:
#include<stdio.h>
#include<string.h>
#include<string>
#include<algorithm>
#include<queue>
#include<math.h>
#define Max 450
#define inf 0x3f3f3f3f
using namespace std;
struct Node{
int x;
int y;
int id;
double dis;
}p[400],b[Max];
struct Edge{
int next;
int to;
}edge[Max*Max];
bool vis[Max];
int link[Max];
int head[Max];
int ans[Max];
int num_edge;
double get_dis(int x1,int y1,int x2,int y2) //计算两点的距离
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
void add_edge(int x,int y)
{
edge[++num_edge].next=head[x];
edge[num_edge].to=y;
head[x]=num_edge;
}
bool find(int num) //模板
{
int i,u;
for(i=head[num];i;i=edge[i].next)
{
u=edge[i].to;
if(!vis[u])
{
vis[u]=1;
if(!link[u]||find(link[u]))
{
link[u]=num;
ans[num]=u;
return true;
}
}
}
return false;
}
int main()
{
int n,m,x,y,cnt;
double d1,d2;
struct Node temp;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d %d",&p[i].x,&p[i].y);
p[i].id=i;
if(i>1) //获得距离
{
p[i-1].dis=get_dis(p[i-1].x,p[i-1].y,p[i].x,p[i].y); //计算前后两个目标点的距离
}
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
b[i].id=i;
b[i].x=x;
b[i].y=y;
for(int j=1;j<n;j++) //
{
d1=get_dis(x,y,p[j].x,p[j].y);
d2=get_dis(x,y,p[j+1].x,p[j+1].y);
if(d1+d2<=2*p[j].dis) //可以赶回来 就把他们连起来
{
// printf("%d %d %d\n",j,j+1,i);
add_edge(j,n+i);
}
}
}
cnt=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(find(i))
cnt++;
}
printf("%d\n",cnt+n);
for(int i=1;i<=n;i++)
{
printf("%d %d",p[i].x,p[i].y);
if(i!=n)
printf(" ");
if(i!=n&&ans[i])
{
printf("%d %d ",b[ans[i]-n].x,b[ans[i]-n].y);
}
}
printf("\n");
return 0;
}
二、KM算法
求带权值的二分图的最优匹配(还是直上模板和题目,还是给个链接吧,网上貌似很多,图都一样。。。。)
练习题:
奔小康赚大钱
模板:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<algorithm>
#define Max 305
#define inf 0x3f3f3f3f
#define max(a,b) a>b?a:b;
using namespace std;
int min(int x,int y)
{
if(x>y) return y;
else return x;
}
int love[Max][Max];//记录好感度
int e_girl[Max];//记录期望值
int e_boy[Max];
bool vis_girl[Max]; //女孩是否匹配过
bool vis_boy[Max]; //男孩是否匹配过
int match[Max]; //记录匹配到的对象,boy匹配到的girl
int slack[Max]; //记录需要增加或者减少的标记
int N; //最大的人数
bool dfs(int girl) //匈牙利求匹配
{
vis_girl[girl]=true;
for(int i=0;i<N;i++)
{
if(!vis_boy[i])
{
int gap=e_girl[girl]+e_boy[i];
if(gap==love[girl][i]) //如果标签和边的值相等,尝试匹配
{
vis_boy[i]=true;
if(match[i]==-1||dfs(match[i])) //如果没有匹配或者可以找到其他人
{
match[i]=girl;
return true;
}
}
else
{
//还差多少能获得该对象,用于更新标签,也就是期望
slack[i]=min(slack[i],gap-love[girl][i]);
}
}
}
return false;
}
int KM()
{
memset(match,-1,sizeof(match));
memset(e_boy,0,sizeof(e_boy));
for(int i=0;i<N;i++)
{
e_girl[i]=love[i][0]; //找到初始期望,所有边值最大的那个
for(int j=1;j<N;j++)
{
e_girl[i]=max(e_girl[i],love[i][j]);
}
}
for(int i=0;i<N;i++)
{
memset(slack,inf,sizeof(slack)); //每次刷新期望
while(1)
{
memset(vis_girl,false,sizeof(vis_girl));
memset(vis_boy,false,sizeof(vis_boy));
if(dfs(i)) break;//找到一个pass
int d=inf;
for(int j=0;j<N;j++)
{
//找到没有匹配过最小的期望,更新,保证每次更新后能再次匹配
if(!vis_boy[j]) d=min(d,slack[j]);
}
for(int j=0;j<N;j++)
{
if(vis_girl[j]) e_girl[j]-=d; //女生降低期望
if(vis_boy[j]) e_boy[j]+=d; //男生提高要求
else slack[j]-=d; //没有访问过 因为女生期望降低,所以slack减少
}
}
}
int res=0;
for(int i=0;i<N;i++)
{
res+=love[match[i]][i];
}
return res;
}
int main()
{
while(scanf("%d",&N)!=EOF)
{
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
scanf("%d",&love[i][j]);
}
}
printf("%d\n",KM());
}
return 0;
}
三、稳定婚姻匹配(GS算法)
核心就是不断找女朋友 (所以又叫女朋友算法,)
思路很简单,但是需要定义很多变量,反正就是一直找,直到全部遍历完成或者全部配对成功。
模板练习:
[国家集训队]稳定婚姻
模板(使用了map记录名字):
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<queue>
#include<stack>
#include<map>
#include<iostream>
#include<algorithm>
#define Max 510
#define max(a,b) a>b?a:b;
#define min(a,b) a>b?b:a;
using namespace std;
map<string ,int > boy;//因为名字可能相同,。。。
map<string ,int > girl;
map<string ,int >::iterator it;
string boyName[Max],girlName[Max];//男女的名字
int relation[Max][Max];
int manpos[Max];
int boyMatch[Max];
int girlMatch[Max];
int boyList[Max][Max];//男孩清单
int girlList[Max][Max];//女孩清单
int cnt;
int a[Max];
queue<int> q;//队列
int main()
{
string temp1,temp2,temp;//转化字符
int n;
char name[100],name1[100];//名字输入
while(scanf("%d",&n)!=EOF)
{
cnt=0;
boy.clear(),girl.clear();
for(int i=1;i<=n;i++)
{
scanf("%s",name);
temp1=name;
boyName[i]=temp1;//记录
boy[temp1]=i;
for(int j=1;j<=n;j++)
{
scanf("%s",name1);
temp2=name1;
if(!girl[temp2])
{
girl[temp2]=++cnt;
girlName[cnt]=temp2;
}
boyList[i][j]=girl[temp2];//转化为数字
relation[i][girl[temp2]]=j;//i 到j 的优先级为 j
}
}
for(int i=1;i<=n;++i)
{
scanf("%s",name);
temp=name;
int id=girl[temp]; //找到女孩的编号
for(int j=1;j<=n;j++)
{
scanf("%s",name1);
int boy_id=boy[name1];//
girlList[id][j]=boy_id;
relation[id][boy_id]=j;
}
}
while(q.size()) q.pop();
for(int i=1;i<=n;i++)
{
q.push(i);//把所有的男生全部压人
}
for(int i=1;i<=n;i++)
{
boyMatch[i]=girlMatch[i]=-1;//-1表示没有匹配
manpos[i]=0;
}
while(q.size())
{
int s=q.size();//目前还有多少人没匹配
for(int i=1;i<=s;i++)
{
int now=q.front();//获得男孩序号
++manpos[now]; //匹配次数增加
q.pop();
if(girlMatch[boyList[now][manpos[now]]]==-1) //没男朋友
{
girlMatch[boyList[now][manpos[now]]]=now;//匹配
boyMatch[now]=boyList[now][manpos[now]];// 记录
}
else //比较优先级
{
if(relation[boyList[now][manpos[now]]][now]<relation[boyList[now][manpos[now]]][girlMatch[boyList[now][manpos[now]]]])
{
boyMatch[girlMatch[boyList[now][manpos[now]]]]=-1;//把前男友鸽了
q.push(girlMatch[boyList[now][manpos[now]]]);
girlMatch[boyList[now][manpos[now]]]=now;//和最新的在一起
boyMatch[now]=boyList[now][manpos[now]];
}
else //还是和原来的在一起
{
q.push(now);//本次匹配失败
}
}
}
}
for(it=boy.begin();it!=boy.end();++it)
{
temp=it->first;
temp1=girlName[boyMatch[it->second]];
cout<<temp<<" "<<temp1<<endl;
}
}
return 0;
}
三种匹配算法基础模板都在这了,不过还需要更多的题目训练自己。(稳定婚姻算法还获得了2012诺贝尔经济学奖,感觉我也会,狗头~~~)
感觉现在网络流和点分治一起看有点慢啊,在机房学习效率真低,还不如晚上自己一小时。加油吧 还有半个月,省选题目尽量多刷,树和图我都要!