http://poj.org/problem?id=3020
上一个题是最小覆盖,这个是最小路径覆盖,通过这两个题学了一下最小覆盖和最小路径覆盖的概念,了解基本的构图方法
最小路径覆盖=n-最大匹配 详细证明:http://hi.baidu.com/cjhh314/blog/item/ded8d31f15d7510c304e1591.html
感觉很好,再粘一下
在一个PXP的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通子集. 由上面可以得出: 1.一个单独的顶点是一条路径; 2.如果存在一路径p1,p2,......pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,......pk不再与其它的顶点之间存在有向边. 最小路径覆盖就是找出最小的路径条数,使之成为P的一个路径覆盖. 路径覆盖与二分图匹配的关系: 最小路径覆盖=|P|-最大匹配数; 其中最大匹配数的求法是把P中的每个顶点pi分成两个顶点pi'与pi'',如果在p中存在一条pi到pj的边,那么在二分图P'中就有一条连接pi'与pj''的无向边;这里pi' 就是p中pi的出边,pj''就是p中pj 的一条入边; 对于公式:最小路径覆盖=|P|-最大匹配数;可以这么来理解; 如果匹配数为零,那么P中不存在有向边,于是显然有: 最小路径覆盖=|P|-最大匹配数=|P|-0=|P|;即P的最小路径覆盖数为|P|; P'中不在于匹配边时,路径覆盖数为|P|; 如果在P'中增加一条匹配边pi'-->pj'',那么在图P的路径覆盖中就存在一条由pi连接pj的边,也就是说pi与pj 在一条路径上,于是路径覆盖数就可以减少一个; 如此继续增加匹配边,每增加一条,路径覆盖数就减少一条;直到匹配边不能继续增加时,路径覆盖数也不能再减少了,此时就有了前面的公式;但是这里只 是说话了每条匹配边对应于路径覆盖中的一条路径上的一条连接两个点之间的有向边;下面来说明一个路径覆盖中的每条连接两个顶点之间的有向边对应于一条匹配 边; 与前面类似,对于路径覆盖中的每条连接两个顶点之间的每条有向边pi--->pj,我们可以在匹配图中对应做一条连接pi'与pj''的边, 显然这样做出来图的是一个匹配图(这一点用反证法很容易证明,如果得到的图不是一个匹配图,那么这个图中必定存在这样两条边 pi'---pj'' 及 pi' ----pk'',(j!=k),那么在路径覆盖图中就存在了两条边pi-->pj, pi--->pk ,那边从pi出发的路径就不止一条了,这与路径覆盖图是矛盾的;还有另外一种情况就是存在pi'---pj'',pk'---pj'',这种情况也类似可证); 至此,就说明了匹配边与路径覆盖图中连接两顶点之间边的一一对应关系,那么也就说明了前面的公式成立! |
代码:
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;
int a[42][12],n;
int link[405],visit[405];
int map[405][405];
bool dfs(int x)
{
for(int i=1;i<=n;i++)
{
if(map[x][i]&&!visit[i])
{
visit[i]=1;
if(link[i]==0||dfs(link[i]))
{
link[i]=x;
return true;
}
}
}
return false;
}
int MaxMatch()
{
int SUM=0;
memset(link,0,sizeof(link));
for(int i=1;i<=n;i++)
{
memset(visit,0,sizeof(visit));
if(dfs(i))
SUM++;
}
return SUM;
}
int main()
{
int CASE,r,c,i,j;
char ch;
cin>>CASE;
while(CASE--)
{ n=0;
memset(a,0,sizeof(a));
cin>>r>>c;
for(i=1;i<=r;i++)
{
for(j=1;j<=c;j++)
{
cin>>ch;
if(ch=='*')
a[i][j]=++n;
}
}
memset(map, 0,sizeof(map));
for(i=1;i<=r;i++)
for(j=1;j<=c;j++)
if(a[i][j])
{
if(a[i-1][j])map[a[i][j]][a[i-1][j]]=1;
if(a[i+1][j])map[a[i][j]][a[i+1][j]]=1;
if(a[i][j+1])map[a[i][j]][a[i][j+1]]=1;
if(a[i][j-1])map[a[i][j]][a[i][j-1]]=1;
}
int ans=MaxMatch();
ans=n-ans/2;
cout<<ans<<endl;
}
return 0;
}
把坐标和为奇数的点放到左边,坐标和为偶数的点放到右边,这样最大匹配完就不用除以2了
改一下
代码:
#include<iostream>
#include<string>
#include<cstring>
#include<cstdio>
using namespace std;
int a[42][12],n,m;
int link[405],vs[405];
int map[405][405];
bool dfs(int x)
{
for(int i=1;i<=m;i++)//二排的点
{
if(map[x][i]&&!vs[i])
{
vs[i]=1;
if(link[i]==0||dfs(link[i]))
{
link[i]=x;
return 1;
}
}
}
return 0;
}
int MaxMatch()
{
int SUM=0;
memset(link,0,sizeof(link));
for(int i=1;i<=n;i++)//一排的点
{
memset(vs,0,sizeof(vs));
if(dfs(i))
SUM++;
}
return SUM;
}
int main()
{
int CASE,r,c,i,j;
char ch;
scanf("%d",&CASE);
while(CASE--)
{ n=0,m=0;
memset(a,0,sizeof(a));
scanf("%d%d",&r,&c);
for(i=1;i<=r;i++)
{ getchar();//!!!
for(j=1;j<=c;j++)
{
//cin>>ch;
scanf("%c",&ch);
if(ch=='*')
{
if((i+j)%2==1) a[i][j]=++n;
else a[i][j]=++m;
}
}
}
memset(map, 0,sizeof(map));
for(i=1;i<=r;i++)
for(j=1;j<=c;j++)
if(a[i][j]&&(i+j)%2==1)
{
if(a[i-1][j])map[a[i][j]][a[i-1][j]]=1;
if(a[i+1][j])map[a[i][j]][a[i+1][j]]=1;
if(a[i][j+1])map[a[i][j]][a[i][j+1]]=1;
if(a[i][j-1])map[a[i][j]][a[i][j-1]]=1;
}
int ans=MaxMatch();
ans=n+m-ans;//总点数-匹配数!!
printf("%d\n",ans);
}
return 0;
}