您的位置:首页 > 其它

二分图的基础与基本应用:POJ 1469&&POJ 3041&&HDU 2255&&HDU1533

2013-08-18 00:20 459 查看
先把最基础的概念搞清楚:

二分图:有两组顶点,一组顶点记为 S1 ,另一组记为 S2 , S1 和 S2 没有公共的元素,并且所有的边都是连接 S1 和 S2 中的点的,对于 S1 和 S2 本身,它们内部的任何两个点都没有边相连,这样的无向图就叫二分图。

点覆盖集:即是一个点集,使得所有边至少有一个端点在集合里。

边覆盖集:即是一个边集,使得所有点都与集合里的边邻接。(其实我觉得这句话很难理解)

但是结合这句呢:在一个P*P的有向图中,最小路径覆盖=|P|-最大匹配数。(因为有匹配数的存在,本来要P个边的覆盖因此减小了)

二分图的最小顶点覆盖数等于最大匹配数。

匈牙利算法:主要二分图的最大匹配数。其实我觉得自学这东西不适合我,可能以前而到现在仍受体制化的影响,有老师真好(有依赖感了)。。并且我觉得有些自己理解花的时间比别人讲的时间要多很多很多!!!

等等。。。

除了点覆盖数还有边覆盖数。。。

下面M是G的一个匹配。
M-交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条M-交错路。
M-饱和点:对于v∈V(G),如果v与M中的某条边关联,则称v是M-饱和点,否则称v是非M-饱和点。
M-可增广路:p是一条M-交错路,如果p的起点和终点都是非M-饱和点,则称p为M-可增广路。(不要和流网络中的增广路径弄混了)
匈牙利算法:(用于求最大匹配数)

COURSES POJ 1469

一道匈牙利算法的模板题:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int a[110][310];//邻接矩阵。用来存储边的信息,连接或没连接
int vis[310],b1[310],m;//一个用来搜索的标记,一个记录点匹配点的编号
int dfs(int x)
{
int i;
for(i=1;i<=m;i++)
if(a[x][i]&&!vis[i])//连通了并且没被标记
{
vis[i]=1;//进行标记
if(b1[i]==0||dfs(b1[i]))//没有被匹配或者前面的有增广路
{
b1[i]=x;//进行记录
return 1;//返回能匹配
}
}
return 0;//返回没有被匹配
}
int main()
{
int t,n,i,j,a2,a1,s;
scanf("%d",&t);
while(t--)
{
memset(a,0,sizeof(a));
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++)
{
scanf("%d",&a1);
while(a1--)
{
scanf("%d",&a2);
a[i][a2]=1;
}
}
s=0;
memset(b1,0,sizeof(b1));
for(i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));//每次都要更新
if(dfs(i))
s++;
}
if(s==n)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}


再就是求最小顶点覆盖数:(因为理论上就等于最大匹配数)

Asteroids POJ 3041

直接上匈牙利算法求最大匹配数就行了。。。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<string.h>
using namespace std;
int a[505][505],vis[505],b1[505],n;
int dfs(int x)
{
int i;
for(i=1;i<=n;i++)
{
if(a[x][i]&&!vis[i])
{
vis[i]=1;
if(b1[i]==0||dfs(b1[i]))
{
b1[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{
int m,a1,a2,s,i;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(a,0,sizeof(a));
while(m--)
{
scanf("%d%d",&a1,&a2);
a[a1][a2]=1;
}
memset(b1,0,sizeof(b1));
s=0;
for(i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i))
s++;
}
printf("%d\n",s);
}
return 0;
}


之后就是求最大权匹配:KM算法。。。

本人觉得有好多细节部分需要多次编写与记忆!

突出的一点要用顶标来计算,于是乎就多了两个数组,之后再交错树那里又多了一个数组!

然而判断方面因为有两顶标,就又多了一个数组。。。算算要开7个数组吧!!!(其中有两对)

奔小康赚大钱 HDU 2255

思路具体参见:/article/4990829.html

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int vix[310],viy[310],link[310],a[310][310],lx[310],ly[310],nx,ny,slack[310];
int inf=1000005;
int dfs(int x)//跟匈牙利算法差不多
{
int i,temp;
vix[x]=1;//不同点
for(i=1;i<=ny;i++)
{
if(viy[i])
continue;
temp=lx[x]+ly[i]-a[x][i];//找的是相等子图
if(temp==0)
{
viy[i]=1;
if(link[i]==-1||dfs(link[i]))
{
link[i]=x;
return 1;
}
}
else if(slack[i]>temp)
slack[i]=temp;
}
return 0;
}
int main()
{
int i,j,s,n;
while(scanf("%d",&n)!=EOF)
{
nx=ny=n;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&a[i][j]);
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));
memset(link,-1,sizeof(link));
for(i=1;i<=nx;i++)
for(j=1;j<=ny;j++)
if(a[i][j]>lx[i])
lx[i]=a[i][j];
for(i=1;i<=nx;i++)
{
for(j=1;j<=nx;j++)
slack[j]=inf;
while(1)
{
memset(vix,0,sizeof(vix));//记录搜索的每次都更新
memset(viy,0,sizeof(viy));//如上
if(dfs(i))//有匹配
break;
int d=inf;//下面是核心:凑成一个匹配出来
for(j=1;j<=ny;j++)
if(!viy[j]&&d>slack[j])
d=slack[j];
for(j=1;j<=nx;j++)
if(vix[j])
lx[j]-=d;
for(j=1;j<=ny;j++)
if(viy[j])
ly[j]+=d;
else
slack[j]-=d;
}
}
s=0;
for(i=1;i<=ny;i++)
if(link[i]!=-1)
s+=a[link[i]][i];
printf("%d\n",s);
}
return 0;
}


经过渊神的细心指导,总算弄懂清楚不少了!在此十分感谢!!!

根据KM算法,找不到匹配时,就要用交错树来凑成一个!(当然希望每进行一次,能凑成一个。。。于是就有slack[]数组了)

vix[]与viy[]是判断是否在交错树中的。。。

根据算法:在树中的lx[]减d,ly[]加d。。。(当然这个值d得在交错树外面找啊,因为进入交错树的都是有匹配的。。。)

最关键的:因为呢,slack[j] = lx[i] + ly[j] - w[i][j],而lx[]减小,其他的不变,(ly[]是交错树外面的),那么slack[j]也要减小d了。。。

时刻谨记着二分图是以左边为基点,向右边进行查找的(一旦左边的向下移动了,必定已经找到了。。。)

之后我发现求最大权匹配和最小权匹配是多么的不同啊!!

在建图的时候,(将每条边的权值变为负数。然后lx[i]初始化为-INT_MAX,结果输出-ans)!!,就可以得到最小权值。

这句话的重点在:要非常大啊!!

Going Home HDU1533

废话也不说了,先化成二分图的形式。。。之后慢慢比对上面的代码!

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#include<string.h>
using namespace std;
int vix[105],viy[105],link[105],b[105][105],lx[105],ly[105],d,slack[105];
int inf=100000005;//要非常大,少个零就超时!!
struct line
{
int x;
int y;
}a1[105],b1[105];
int dfs(int x)//跟匈牙利算法差不多
{
int i,temp;
vix[x]=1;//不同点
for(i=1;i<=d;i++)
{
if(viy[i])
continue;
temp=lx[x]+ly[i]-b[x][i];//找的是相等子图
if(temp==0)
{
viy[i]=1;
if(link[i]==-1||dfs(link[i]))
{
link[i]=x;
return 1;
}
}
else if(slack[i]>temp)
slack[i]=temp;
}
return 0;
}
int main()
{
char a[105][105];
int i,n,m,d1,j,s;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(m==0&&n==0)
break;
for(i=0;i<n;i++)
scanf("%s",a[i]);
d=0;d1=0;
for(i=0;i<n;i++)
for(j=0;j<m;j++)
{
if(a[i][j]=='m')
{
d++;
a1[d].x=i;
a1[d].y=j;
}
if(a[i][j]=='H')
{
d1++;
b1[d1].x=i;
b1[d1].y=j;
}
}
for(i=1;i<=d;i++)
for(j=1;j<=d;j++)
b[i][j]=-abs(a1[i].x-b1[j].x)-abs(a1[i].y-b1[j].y);//全变成负值
memset(lx,-inf,sizeof(lx));//这里初始为负的,因为下面是负值,你要求较大值。。不然全为零了
memset(ly,0,sizeof(ly));
memset(link,-1,sizeof(link));
for(i=1;i<=d;i++)
for(j=1;j<=d;j++)
if(b[i][j]>lx[i])//赋负值的原因
lx[i]=b[i][j];
for(i=1;i<=d;i++)
{
for(j=1;j<=d;j++)
slack[j]=inf;
while(1)
{
memset(vix,0,sizeof(vix));//记录搜索的每次都更新
memset(viy,0,sizeof(viy));//如上
if(dfs(i))//有匹配
break;
int h=inf;//下面是核心:凑成一个匹配出来
for(j=1;j<=d;j++)
if(!viy[j]&&h>slack[j])
h=slack[j];
for(j=1;j<=d;j++)
{
if(vix[j])
lx[j]-=h;
if(viy[j])
ly[j]+=h;
else
slack[j]-=h;
}
}
}
s=0;
for(i=1;i<=d;i++)
s+=b[link[i]][i];
printf("%d\n",-s);

}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: