紫书:二分图匹配 最大流解决

网络流的一个经典应用是二分图匹配
匹配是指:两两没有公共点的边集。
二分图是指:可以把结点集分成两部分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;
}

你可能感兴趣的:(UVA,心得,紫书训练)