/*
该模板不用建立超级源点和超级汇点,
直接把二分图中对应的边连接即可
*/
int V;//顶点数
vector<int> G[N];//图的邻接表表示
int match[N];//所匹配的点顶点
bool used[N];//dfs中用到的访问标志
//向图中增加一条连接u和v的边
void addedge(int u,int v)
{
G[u].push_back(v);
G[v].push_back(u);
}
//通过dfs寻找增广路径
bool dfs(int v)
{
used[v]=true;
for(int i=0;i<G[v].size();i++)
{
int u=G[v][i];
int w=match[u];
if(w<0||!used[w]&&dfs(w))
{
match[v]=u;
match[u]=v;
return true;
}
}
return false;
}
//求解二分图的最大匹配
int bipartite_matching()
{
int ans=0;
memset(match,-1,sizeof(match));
for(int v=0;v<V;v++)
{
if(match[v]<0)
{
memset(used,0,sizeof(used));
if(dfs(v))
ans++;
}
}
return ans;
}
/*
该模板是基于最大流算法的,
因此需要建立超级源点和超级汇点
*/
struct edge
{
int to,cap,rev;//终点,容量,反向边
};
vector<edge>G[N];//图的邻接矩阵表示
bool used[N];//dfs中用到的访问标记
int can[N][N];//存原始图,c[i][j]=1表示i到j可达
int n,m;//左点集的个数和右点集的个数
int s,t;//超级源点和超级汇点
void addedge(int u,int v,int w)
{
G[u].push_back((edge){v,w,G[v].size()});
G[v].push_back((edge){u,0,G[u].size()-1});
}
int dfs(int v,int t,int f)
{
if(v==t)
return f;
used[v]=true;
for(int i=0;i<G[v].size();i++)
{
edge &e=G[v][i];
if(!used[e.to]&&e.cap>0)
{
int d=dfs(e.to,t,min(f,e.cap));
if(d>0)
{
e.cap-=d;
G[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
//求解从s到t上的最大流
int max_flow(int s,int t)
{
int flow=0;
for(;;)
{
memset(used,0,sizeof(used));
int f=dfs(s,t,INF);
if(f==0)
return flow;
flow+=f;
}
}
//建图
void Get_map()
{
s=0,t=m+1;//按实际情况确定s和t
//超级源点向左点集连边
for(int i=1;i<=n;i++)
addedge(s,i,1);
//右点集向超级汇点连边
for(int i=n+1;i<=m;i++)
addedge(i,t,1);
//左点集向右点集连边
for(int i=1;i<=m;i++)
{
for(int j=1;j<=m;j++)
if(can[i][j])
addedge(i,j,1);
}
}
有向无环图(DAG)的最小路径覆盖
有向无环图中,路径覆盖就是在图中找一些路径,使之覆盖了图中的所有顶点,且任何一个顶点有且只有一条路径与之关联(如果把这些路径中的每条路径从它的起始点走到它的终点,那么恰好可以经过图中的每个顶点一次且仅一次)。最小路径覆盖就是找出最小的路径条数,使之成为原图的一个路径覆盖。
在《图论及应用》中有这样一张图:
由图中可以看出,我们主要把有向无环图中每个顶点都拆成两个,然后在连上对应的边,那么所求问题就可以转化为:
最小路径覆盖=(原图)顶点数-对应的二分图的最大匹配数
把图转化后,套用二分图匹配模板即可求解(注意:转化后顶点数要乘2,求解最终答案时要除2)