1.最小点覆盖=最大匹配
eg.1 poj1325 Machine Schedule
题意:有两个机器A和B,A机器有n个模式,B机器有m个模式,两个机器最初在0模式。有k个作业,每个作业有三个参数i,a,b,其中i代表作业编号,a和b代表第i作业要么在A机器的a模式下完成或在B机器的b模式下完成,问两个机器总共最少变换多少次可以完成所有作业
我们将两台机器的每个模式作为一个顶点,如果作业作业需要机器A的x模式和机器B的y模式,就将x-y相连,然后用最少的顶点覆盖所有的边即可
代码
// 最小点覆盖
// by andyc_03
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,m,ma[305][305],link[305],vis[305];
int dfs(int x)
{
for(int i=1;i<=m;i++)
{
if(!vis[i] && ma[x][i]==1)
{
vis[i]=1;
if(!link[i] || dfs(link[i])==1)
{
link[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("a.in","r",stdin);
int t;
while(scanf("%d%d%d",&n,&m,&t)!=EOF && n)
{
memset(ma,0,sizeof(ma));
memset(link,0,sizeof(link));
int x,y,z;
for(int i=1;i<=t;i++)
{
scanf("%d%d%d",&x,&y,&z);
ma[y][z]=1;
}
int cnt=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i)) cnt++;
}
printf("%d\n",cnt);
}
return 0;
}
eg.2 poj3041 Asteroids
题意:有一个N*N的网格,该网格有K个障碍物.你有一把武器,每次你使用武器可以清除该网格特定行或列的所有障碍.问你最少需要使用多少次武器能清除网格的所有障碍物?
可以将行作为左侧部分,列作为右侧部分,若节点(x,y)有障碍,那么将x-y连边,然后就是需要求最小点覆盖
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,k;
const int maxn=1005;
const int maxm=2e4+5;
struct edge
{
int to,nxt;
}e[maxm];
int cnt,head[maxn];
void add(int x,int y)
{
e[++cnt].to=y;
e[cnt].nxt=head[x];
head[x]=cnt;
}
int link[maxn],vis[maxn];
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(!vis[to])
{
vis[to]=1;
if(link[to]==-1 || dfs(link[to])==1)
{
link[to]=x;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(~scanf("%d%d",&n,&k))
{
int x,y;
for(int i=1;i<=k;i++)
{
scanf("%d%d",&x,&y);
add(x,n+y);
add(n+y,x);
}
memset(link,-1,sizeof(link));
int res=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
res+=dfs(i);
}
printf("%d\n",res);
}
return 0;
}
eg.3 poj2226 Muddy Fields
题意:一个由r行c列方格组成的田地,里面有若干个方格充满泥泞,其余方格都是草。要用长度不限,宽度为1的长木板来覆盖这些泥方格,但不能覆盖草地。最少要用多少个长木板。
将连着的竖着或者横着的一排泥泞点编成一个号,然后将每个点竖着和横着的点的号连边,求最小点覆盖
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,m;
int ma[55][55],num[55][55],numm[55][55];
const int maxn=2505;
const int maxm=6250005;
struct edge
{
int to,nxt;
}e[maxm<<1];
int cnt,head[maxn];
void add(int x,int y)
{
e[++cnt].to=y;
e[cnt].nxt=head[x];
head[x]=cnt;
}
int link[maxn],vis[maxn];
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(!vis[to])
{
vis[to]=1;
if(link[to]==-1 || dfs(link[to])==1)
{
link[to]=x;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(~scanf("%d%d",&n,&m))
{
for(int i=1;i<=n;i++)
{
getchar();
for(int j=1;j<=m;j++)
{
char cc=getchar();
if(cc=='*') ma[i][j]=1;
else ma[i][j]=0;
}
}
int cnt,cntt;
for(int i=1;i<=n;i++)
{
int flag=1;
for(int j=1;j<=m;j++)
{
if(ma[i][j]==1)
{
if(flag) cnt++;
flag=0;
num[i][j]=cnt;
}
else flag=1;
}
}
cntt=cnt;
for(int i=1;i<=m;i++)
{
int flag=1;
for(int j=1;j<=n;j++)
{
if(ma[j][i]==1)
{
if(flag) cnt++;
flag=0;
numm[j][i]=cnt;
}
else flag=1;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(ma[i][j]==1)
add(num[i][j],numm[i][j]),add(numm[i][j],num[i][j]);
memset(link,-1,sizeof(link));
int res=0;
for(int i=1;i<=cntt;i++)
{
memset(vis,0,sizeof(vis));
res+=dfs(i);
}
printf("%d\n",res);
}
return 0;
}
2.最小边覆盖
eg.1 poj3020 Antenna Placement
题意:
*--代表城市,o--代表空地
给城市安装无线网,一个无线网最多可以覆盖两座城市,问覆盖所有城市最少要用多少无线网。
我们可以利用交替染色的思想,这样1*2无线网就相当于是颜色1和颜色2的最大匹配数,那么我们所求即可转化为最小边覆盖
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
int n,m,flag[405],vis[405];
int ma[42][12],col[42][12],num[42][12];
const int maxm=405*405;
const int maxn=405;
int head[405],tot=0,link[405];
struct edge
{
int to,nxt;
}e[maxm<<1];
void add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
e[++tot].to=x;
e[tot].nxt=head[y];
head[y]=tot;
}
int dfs(int x)
{
for(int i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(!vis[to])
{
vis[to]=1;
if(link[to]==-1 || dfs(link[to]))
{
link[to]=x;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
int t; scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m); tot=0;
memset(col,0,sizeof(col));
memset(num,0,sizeof(num));
memset(ma,0,sizeof(ma));
memset(flag,0,sizeof(flag));
memset(link,-1,sizeof(link));
memset(head,0,sizeof(head));
int res=0,cnt=0;
for(int i=1;i<=n;i++)
{
getchar();
for(int j=1;j<=m;j++)
{
char cc=getchar();
if(cc=='*') res++,ma[i][j]=1,num[i][j]=++cnt;
else ma[i][j]=0;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
col[i][j]=(j+i-1)%2;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(ma[i][j]==1 && ma[i][j+1]==1)
{
if(col[i][j]==1) flag[num[i][j]]=1;
if(col[i][j+1]==1) flag[num[i][j+1]]=1;
add(num[i][j],num[i][j+1]);
}
if(ma[i][j]==1 && ma[i+1][j]==1)
{
if(col[i][j]==1) flag[num[i][j]]=1;
if(col[i+1][j]==1) flag[num[i+1][j]]=1;
add(num[i][j],num[i+1][j]);
}
}
for(int i=1;i<=cnt;i++)
{
if(!flag[i]) continue;
memset(vis,0,sizeof(vis));
res-=dfs(i);
}
printf("%d\n",res);
}
return 0;
}
3.最大点独立集
eg.1 poj1466 Girls and Boys
题意:有n个学生,并且给出男女之间的暧昧关系。现在求一个学习小组,使得里面的任何两个人之间不能有暧昧关系。
求这个学习小组最多能够有多少人?
最大点独立集=n-最大匹配
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
int n,ma[505][505],link[505],vis[505];
int dfs(int x)
{
for(int i=1;i<=n;i++)
{
if(!vis[i] && ma[x][i]==1)
{
vis[i]=1;
if(!link[i] || dfs(link[i])==1)
{
link[i]=x;
return 1;
}
}
}
return 0;
}
int main()
{
freopen("a.in","r",stdin);
while(scanf("%d",&n)!=EOF)
{
memset(ma,0,sizeof(ma));
memset(link,0,sizeof(link));
int k,x,y;
for(int i=1;i<=n;i++)
{
scanf("%d: (%d)",&k,&x);
k++;
for(int j=1;j<=x;j++)
{
scanf("%d",&y);
y++;
ma[k][y]=1;
}
}
int cnt=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i)) cnt++;
}
printf("%d\n",n-cnt/2);
}
return 0;
}
4.二分图最大权匹配——KM算法
KM算法,用于求二分图匹配的最佳匹配。最佳匹配,就是带权二分图的权值最大的完备匹配称为最佳匹配。 完备匹配是X部中的每一个顶点都与Y部中的一个顶点匹配,或者Y部中的每一个顶点也与X部中的一个顶点匹配。
对于原图中的任意一个结点,给定一个顶标值,使得
我们可以得到KM算法流程:
- 初始化可行顶标的值 (设定
)
- 用匈牙利算法寻找相等子图的完备匹配
- 若未找到增广路则修改可行顶标的值
- 重复(2)(3)直到找到相等子图的完备匹配为止
下面就具体的说说第3步中修改顶标的方法:
正在增广的增广路径上属于集合X的所有点减去一个常数dt,属于集合Y的所有点加上一个常数dt,这样原来的相等子图依旧在相等子图里。
dt的取值:如果dt太小会导致没有新的可行边加入,如果dt太大会导致不再成立,所以我们可以取
,这样能保证每次至少有一条边加入新的相等子图
这样做的时间复杂度为 ,继续考虑优化,如果在记录一个slack数组,用于记录最优的dt,每次增广的过程中再进行修改,这样的时间复杂度看起来就是
,但是,还是可以构造匹配的部分跑到
的数据,所以最坏的时间复杂度仍然是
。
但是!! 如果我们把dfs改成bfs,它的复杂度就会真正降到了
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
typedef long long ll;
const int maxn=505;
const int maxm=125005;
const ll inf=1e18;
struct edge
{
int to,nxt,v;
}e[maxm<<1];
ll ma[maxn][maxn],ex[maxn],ey[maxn];
int link[maxn],vis[maxn];
ll slack[maxn],pre[maxn];
void match(int u)
{
int x,y=0,yy;
ll dt;
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++) slack[i]=inf;
link[y]=u;
while(1)
{
x=link[y];
dt=inf;
vis[y]=1;
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
if(slack[i]>ex[x]+ey[i]-ma[x][i])
{
slack[i]=ex[x]+ey[i]-ma[x][i];
pre[i]=y;
}
if(slack[i]<dt) dt=slack[i],yy=i;
}
for(int i=0;i<=n;i++)
{
if(vis[i]) ex[link[i]]-=dt,ey[i]+=dt;
else slack[i]-=dt;
}
y=yy;
if(link[y]==-1) break;
}
while(y)
{
link[y]=link[pre[y]];
y=pre[y];
}
}
ll KM()
{
memset(link,-1,sizeof(link));
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
match(i);
}
ll res=0;
for(int i=1;i<=n;i++)
if(link[i]!=-1)
res+=ma[link[i]][i];
return res;
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d",&n,&m);
int x,y,z;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
ma[i][j]=-inf;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
ma[x][y]=z;
}
printf("%lld\n",KM());
for(int i=1;i<=n;i++)
printf("%lld ",link[i]);
return 0;
}