DAG的最小路径覆盖
定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。
最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。
最小不相交路径覆盖:每一条路径经过的顶点各不相同。如图,其最小路径覆盖数为3。即1->3>4,2,5。
最小可相交路径覆盖:每一条路径经过的顶点可以相同。如果其最小路径覆盖数为2。即1->3->4,2->3>5。
特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。
以下代码均为自己编写
DAG的最小不相交路径覆盖
算法:把原图的每个点V拆成VxVx和VyVy两个点,如果有一条有向边A->B,那么就加边Ax−>ByAx−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。
证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。
因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。
例题:POJ 2060
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int maxn=5000;
pair<int,int>p1[maxn],p2[maxn];
int aa[maxn],bb[maxn];
int head[maxn],cnt=0;
int vis[maxn];
int inpath[maxn],cx[maxn],cy[maxn];
struct edge
{
int v,nxt;
}edge[maxn*maxn*2+100];
int n,m;
void add_edge(int u,int v)
{
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
int dfs(int u)
{
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
// printf("1111111\n");
int v=edge[i].v;
if(!inpath[v])
{
inpath[v]=1;
if(cy[v]==-1||dfs(cy[v]))
{
cx[u]=v;
cy[v]=u;
return 1;
}
}
}
return 0;
}
void zyz()
{
int res=0;
for(int i=1;i<=n;i++)
{
cy[i]=-1;
}
for(int i=1;i<=n;i++)
{
cx[i]=-1;
}
for(int i=1;i<=n;i++)
{
// printf("%d",i);
if(cx[i]==-1)
{
memset(inpath,0,sizeof(inpath));
//printf("res:%d %d\n",res,cy[i]);
res+=dfs(i);
}
}
printf("%d\n",n-res);
}
int main ()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%d:%d",&aa[i],&bb[i]);
scanf("%d %d",&p1[i].first,&p1[i].second);
scanf("%d %d",&p2[i].first,&p2[i].second);
}
int tot=0;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
if(i==j)
continue;
int ans=0;
ans=(abs(p1[i].first-p2[i].first)+abs(p1[i].second-p2[i].second))+(abs(p2[i].first-p1[j].first)+abs(p2[i].second-p1[j].second));
int cntt=0;
cntt=(aa[j]-aa[i])*60+(bb[j]-bb[i]);
if(ans<=cntt-1)
{
add_edge(i,j);
}
}
}
/*for(int i=1;i<=tot;i++)
{
for(int j=head[i];j!=-1;j=edge[j].nxt)
{
int v;
v=edge[j].v;
printf("%d %d\n",i,v);
}
}*/
zyz();
}
}
DAG的最小可相交路径覆盖
算法:先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。
证明:为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。
例题:POJ 2594
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
using namespace std;
const int maxn=1200;
const int maxm=50000;
int head[maxm],cnt=0;
int inpath[maxn],cx[maxn],cy[maxn];
int g[maxn][maxn];
struct edge
{
int v,nxt;
}edge[maxm*4+100];
int n,m;
void add_edge(int u,int v)
{
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt++;
}
void flyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(!g[i][j])
g[i][j]=(g[i][k]&g[k][j]);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(g[i][j])
add_edge(i,j);
}
}
}
int dfs(int u)
{
for(int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].v;
if(!inpath[v])
{
inpath[v]=1;
if(cy[v]==-1||dfs(cy[v]))
{
cx[u]=v;
cy[v]=u;
return 1;
}
}
}
return 0;
}
void zyz()
{
int res=0;
for(int i=1;i<=n;i++)
{
cy[i]=-1;
}
for(int i=1;i<=n;i++)
{
cx[i]=-1;
}
for(int i=1;i<=n;i++)
{
if(cx[i]==-1)
{
memset(inpath,0,sizeof(inpath));
res+=dfs(i);
// printf("%d\n",res);
}
}
printf("%d\n",n-res);
}
int main ()
{
while(~scanf("%d %d",&n,&m))
{
if(n==0&&m==0)
{
break;
}
memset(g,0,sizeof(g));
memset(head,-1,sizeof(head));
cnt=0;
for(int i=1;i<=m;i++)
{
int xx,yy;
scanf("%d%d",&xx,&yy);
g[xx][yy]=1;
}
flyd();
zyz();
}
}