【问题分析】
有向无环图最小路径覆盖,可以转化成二分图最大匹配问题,从而用最大流解决。
【建模方法】
构造二分图,把原图每个顶点i拆分成二分图X,Y集合中的两个顶点Xi和Yi。对于原图中存在的每条边(i,j),在二分图中连接边(Xi,Yj)。然后把二分图最大匹配模型转化为网络流模型,求网络最大流。
最小路径覆盖的条数,就是原图顶点数,减去二分图最大匹配数。沿着匹配边查找,就是一个路径上的点,输出所有路径即可。
【建模分析】
对于一个路径覆盖,有如下性质:
1、每个顶点属于且只属于一个路径。
2、路径上除终点外,从每个顶点出发只有一条边指向路径上的另一顶点。
所以我们可以把每个顶点理解成两个顶点,一个是出发,一个是目标,建立二分图模型。该二分图的任何一个匹配方案,都对应了一个路径覆盖方案。如果匹配数为0,那么显然路径数=顶点数。每增加一条匹配边,那么路径覆盖数就减少一个,所以路径数=顶点数 - 匹配数。要想使路径数最少,则应最大化匹配数,所以要求二分图的最大匹配。
注意,此建模方法求最小路径覆盖仅适用于有向无环图,如果有环或是无向图,那么有可能求出的一些环覆盖,而不是路径覆盖。
重点说一下找路径的地方
to[u]=ver[i];
mark[ver[i]-n]=1;
这两句话是所有的尾节点记录了下来,然后把每个尾节点标记一下,为什么要减掉n呢,因为我拆点了,多了n个点,这样的话在我递归输出的话,只要不是刚开始的节点就开始不会输出,然后通过递归来输出
#include<stdio.h>
#include<iostream>
#include <string.h>
using namespace std;
const int oo=1e9;
/**oo 表示无穷大*/
const int mm=111111111;
/**mm 表示边的最大数量,记住要是原图的两倍,在加边的时候都是双向的*/
const int mn=999;
/**mn 表示点的最大数量*/
int node,src,dest,edge;
/**node 表示节点数,src 表示源点,dest 表示汇点,edge 统计边数*/
int ver[mm],flow[mm],next[mm];
/**ver 边指向的节点,flow 边的容量 ,next 链表的下一条边*/
int head[mn],work[mn],dis[mn],q[mn],to[mn];
bool mark[mn];
int n,m,x,y;
void prepare(int _node, int _src,int _dest)
{
node=_node,src=_src,dest=_dest;
for(int i=0; i<node; ++i)head[i]=-1;
edge=0;
}
/**增加一条 u 到 v 容量为 c 的边*/
void addedge( int u, int v, int c)
{
ver[edge]=v,flow[edge]=c,next[edge]=head[u],head[u]=edge++;
ver[edge]=u,flow[edge]=0,next[edge]=head[v],head[v]=edge++;
}
/**广搜计算出每个点与源点的最短距离,如果不能到达汇点说明算法结束*/
bool Dinic_bfs()
{
int i,u,v,l,r=0;
for(i=0; i<node; ++i)dis[i]=-1;
dis[q[r++]=src]=0;
for(l=0; l<r; ++l)
for(i=head[u=q[l]]; i>=0; i=next[i])
if(flow[i]&&dis[v=ver[i]]<0)
{
/**这条边必须有剩余容量*/
dis[q[r++]=v]=dis[u]+1;
if(v==dest) return 1;
}
return 0;
}
/**寻找可行流的增广路算法,按节点的距离来找,加快速度*/
int Dinic_dfs( int u, int exp)
{
if(u==dest) return exp;
/**work 是临时链表头,这里用 i 引用它,这样寻找过的边不再寻找*/
for( int &i=work[u],v,tmp; i>=0; i=next[i])
if(flow[i]&&dis[v=ver[i]]==dis[u]+1&&(tmp=Dinic_dfs(v,min(exp,flow[i])))>0)
{
to[u]=ver[i];
mark[ver[i]-n]=1;
flow[i]-=tmp;
flow[i^1]+=tmp;
/**正反向边容量改变*/
return tmp;
}
return 0;
}
int Dicnic_flow()
{
int i,ret=0,delta;
while(Dinic_bfs())
{
for(i=0; i<node; ++i)work[i]=head[i];
while(delta=Dinic_dfs(src,oo))
ret+=delta;
}
return ret;
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n>>m)
{
prepare(n*2+2,0,n*2+1);
for(int i=1;i<=m;i++)
{
cin>>x>>y;
addedge(x,y+n,1);
}
for(int i=1;i<=n;i++)
{
addedge(0,i,1);
addedge(i+n,n*2+1,1);
}
memset(mark,0,sizeof(mark));
memset(to,0,sizeof(to));
int sum=n-Dicnic_flow();
//下面就是递归输出的部分
for(int i=1;i<=n;i++)
{
if(mark[i])//如果被标记证明不是最开始的那个节点,所以先不输出
continue;
cout<<i;
int k=i;
while(to[k])//如果存在,因为是一个链状,所以把这个点-n输出,即这个点的号,然后k=to[k]-n;是递归的主要部分,把尾节点变成头结点,然后继续递归
{
cout<<" "<<to[k]-n;
k=to[k]-n;
}
cout<<endl;
}
cout<<sum<<endl;
}
return 0;
}