网络流的一个经典应用是二分图匹配。
匹配是指:两两没有公共点的边集。
二分图是指:可以把结点集分成两部分X和Y,使得每条边恰好一个端点在 X X X,另一个端点在 Y Y Y。换句话说,同色节点不相邻,进行二染色。
一般在画图的时候,把 X X X结点和 Y Y Y结点画成左右两列。
可以证明一个图是二分图,当且仅当没有奇数圈。
当我们使用 d i n i c dinic dinic跑二分图的时候,复杂度只有 O ( n 1 2 m ) O(n^\frac{1}{2}m) O(n21m),但具体怎么求的之后介绍。
只需要知道复杂度非常优秀即可。
常见二分图匹配问题有两种:
最大基数匹配:包含最多匹配边数。
针对这样的问题,增加一个源点 s s s,一个汇点 t t t,从 s s s到 X X X各连接一个容量为1的弧,同理, t t t和 Y Y Y也连接。对于每条边直接相连,也是容量为1,跑一遍最大流即是最大匹配。
前面容量为1限制了 X X X和 Y Y Y各自只能匹配一次。其实中间的容量是无所谓的,因为已经实现了对唯一匹配的限制。
求出最大流之后,原图中所有流量为1的弧(起点终点在不同边)即是最佳匹配。
针对带权图,需要求出边权最大的匹配。
与此同时,有两种情况,要求这个匹配本身是完美匹配(每个点都被匹配到)。或者不要求。
如果要求的话,把权值赋为相反值,求最小费用最大流即可,加上判断源点和汇点的所有弧是否流量都为1,如果不是,说明不能满足完美匹配。
对于第二种情况,我们只需要每次增广按1增加,同时记录答案取最优即可。
因为是这样的,我们求解的时候可能费用最大的出现在流量较小的时候,但是最小费用最大流是直接计算到最大流。处理的方式很简单,求出最小改进量,如果大于1,令它等于1即可。
引入一道例题:Uva1349
题目要求找若干个有向圈,每个点恰好属于一个圈,要求权值和尽可能小。
对于一个圈,每个点都有唯一的前驱和后驱,对于每条边必定对应某个点的前驱和某个点的后驱。
我们定义每条边就是从前驱指向后驱,每个点拆分成两个点前驱和后驱。
最后实现前驱和后驱唯一匹配即可(同时需要完美匹配),不能够自己匹配自己。(实际上给出的边点不一样必然是不会出现自己匹配自己的。)
代码如下:最小费用最大流求解二分图最小权完美匹配问题。
#include
#define FOR(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
#define ll long long
#define eps 1e-6
#define inf 0x3f3f3f3f
const int maxn=2500;
struct Edge{
int from,to;
ll cap,flow,cost;
};
struct MCMF{
int n,tmp,s,t;
vector<Edge>edges;
vector<int>G[maxn];
int inq[maxn];
ll d[maxn];//spfa
int p[maxn];//上一条弧便于回溯
ll a[maxn];//最小改进量
void init(int n,int s,int t){
this->n=n,this->s=s,this->t=t;
edges.clear();
for(int i=1;i<=n;i++)G[i].clear();
}
void AddEdge(int from,int to,ll cap,ll cost){
edges.push_back((Edge){from,to,cap,0,cost});
edges.push_back((Edge){to,from,0,0,-cost});
tmp=edges.size();
G[from].push_back(tmp-2);
G[to].push_back(tmp-1);
}
bool spfa(int s,int t,ll& flow,ll& cost){
for(int i=0;i<=n;i++)d[i]=inf;
memset(inq,0,sizeof(inq));
d[s]=0,inq[s]=1,p[s]=0,a[s]=inf;
queue<int>Q;
Q.push(s);
while(!Q.empty()){
int u=Q.front();Q.pop();
inq[u]=0;
for(int i=0;i<G[u].size();i++){
Edge& e=edges[G[u][i]];
if(e.cap>e.flow&&d[e.to]>d[u]+e.cost){
d[e.to]=d[u]+e.cost;//松弛
p[e.to]=G[u][i];//记录上一条弧
a[e.to]=min(a[u],e.cap-e.flow);//最小可改进量
if(!inq[e.to]){Q.push(e.to);inq[e.to]=1;}//入队
}
}
}
if(d[t]==inf)return false;//说明不连通了。
flow+=a[t];//如果固定流量的话,可以在flow+a>=k的时候只增广到k,然后终止程序
cost+=d[t]*a[t];
int u=t;
while(u!=s){
edges[p[u]].flow+=a[t];
edges[p[u]^1].flow-=a[t];
u=edges[p[u]].from;
}
return true;
}
ll Mincost(){//绝对不能有负权圈,否则连续最短路的数学证明失效
ll flow=0,cost=0;
while(spfa(s,t,flow,cost));
bool ok=true;
for(int i=0;i<G[n-1].size();i++){
Edge& e=edges[G[n-1][i]];
if(e.flow<1)ok=false;
}
for(int i=0;i<G[n].size();i++){
Edge& e=edges[G[n][i]^1];
if(e.flow<1)ok=false;
}
if(ok)cout<<cost<<endl;
else puts("N");
return flow;
}
}mf;
int main(){
int n,m;
while(~scanf("%d",&n)&&n){
mf.init(2*n+2,2*n+1,2*n+2);
FOR(i,1,n){
mf.AddEdge(2*n+1,i,1,0);
mf.AddEdge(n+i,2*n+2,1,0);
}
FOR(i,1,n){
int j;
while(scanf("%d",&j)&&j){
ll c;scanf("%lld",&c);
mf.AddEdge(i,n+j,1,c);
}
}
mf.Mincost();
}
return 0;
}