/*
該模板不用建立超級源點和超級匯點,
直接把二分圖中對應的邊連接即可
*/
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)