2017/4/15集训队讲课 二分匹配相关套路

PPT基于电子科技大学制作的 二分图&匹配

1,二分图判定与构造
2,二分图匹配(匈牙利算法)

先补充基本概念

http://blog.csdn.net/pi9nc/article/details/11848327

一、二分图最大匹配

定义:匹配是图中一些边的集合,且集合中任意两条边都没有公共点,所有的匹配中,边数最多的就是最大匹配。

二、二分图最小点覆盖
定义:点覆盖是图中一些点的集合,且对于图中所有的边,至少有一个端点属于点覆盖,点数最小的覆盖就是最小点覆盖。
定理:最小点覆盖=最大匹配。

三、二分图最小边覆盖
定义:边覆盖是图中一些边的集合,且对于图中所有的点,至少有一条集合中的边与其相关联,边数最小的覆盖就是最小边覆盖。
定理:最小边覆盖=图中点的个数-最大匹配。
简单证明:先贪心的选一组最大匹配的边加入集合,对于剩下的每个未匹配的点,随便选一条与之关联的边加入集合,得到的集合就是最小边覆盖,所以有:最小边覆盖=最大匹配+图中点的个数-2*最大匹配=图中点的个数-最大匹配。

四、二分图最大独立集
定义:独立集是图中一些点的集合,且图中任意两点之间都不存在边,点数最大的就是最大独立集。
定理:最大独立集=图中点的个数-最大匹配。
简单证明:可以这样来理解,先把所有的点都加入集合,删除最少的点和与其关联的边使得剩下的点相互之间不存在边,我们就得到了最大独立集。所以有:最大独立集=图中点的个数-最小点覆盖=图中点的个数-最大匹配。

五、有向无环图最小不相交路径覆盖
定义:用最少的不相交路径覆盖所有顶点。
定理:把原图中的每个点V拆成Vx和Vy,如果有一条有向边A->B,那么就加边Ax-By。这样就得到了一个二分图,最小路径覆盖=原图的节点数-新图最大匹配。
简单证明:一开始每个点都独立的为一条路径,总共有n条不相交路径。我们每次在二分图里加一条边就相当于把两条路径合成了一条路径,因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义。所以有:最小路径覆盖=原图的节点数-新图最大匹配。

六,有向无环图的最小可相交路径覆盖
定义:用最少的可相交路径覆盖所有顶点。
定理:先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。然后处理方法同上。
简单证明:为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。
七、偏序集的最大反链
定义:偏序集中的最大独立集。
Dilworth定理:对于任意偏序集都有,最大独立集(最大反链)=最小链的划分(最小不相交路径覆盖)。
通过Dilworth定理,我们就可以把偏序集的最大独立集问题转化为最小不相交路径覆盖问题了。

八,二分图的多重匹配

二分图多重匹配分为二分图多重最大匹配与二分图多重最优匹配两种,分别可以用最大流与最大费用最大流解决。

(1)二分图多重最大匹配:
在原图上建立源点S和汇点T,S向每个X方点连一条容量为该X方点L值的边,每个Y方点向T连一条容量为该Y方点L值的边,原来二分图中各边在新的网络中仍存在,容量为1(若该边可以使用多次则容量大于1),求该网络的最大流,就是该二分图多重最大匹配的值。

(2)二分图多重最优匹配:
在原图上建立源点S和汇点T,S向每个X方点连一条容量为该X方点L值、费用为0的边,每个Y方点向T连一条容量为该Y方点L值、费用为0的边,原来二分图中各边在新的网络中仍存在,容量为1(若该边可以使用多次则容量大于1),费用为该边的权值。求该网络的最小费用最大流,就是该二分图多重最优匹配的值。

例题:
【1】POJ1698 Alice’s Chance
将电影作为X方点,每一天作为Y方点(最多50周,每周7天,所以共设350个Y方点),若第i个电影可以在第j天搞就连边(i, j)。每个X方点的L值为该电影总共要搞多少天,每个Y方点的L值为1(每天最多只能搞一个电影),然后求二分图多重最大匹配,若能使所有从源点连向X方点的边都满流,则输出Yes,否则输出No。
【2】POJ2112 Optimal Milking
先预处理求出每两个点(包括挤奶点和牛)间的最短距离,然后将所有挤奶点作为X方点(L值为该挤奶点最多可以容纳多少牛),所有牛作为Y方点(L值为1),Xi和Yj间边的权值为这两个点之间的最短距离(若这两点间不存在路径则此处无边),然后问题就变成了求一个多重匹配,使得每个Y方点都有匹配点且匹配边中权值的最大值最小。
可以枚举最大边权值S,然后,原图中所有权值大于S的边都要删去。若此时图中存在符合要求的多重匹配,则S合法否则S不合法。由于S的合法性是单调的,所以可以二分枚举S。

九、二分图带权最大匹配
定义:每个边都有一组权值,边权之和最大的匹配就是带权最大匹配。
算法:KM算法,复杂度为O(V^3)。具体就不说了,网上有不少资料。
要注意的是,KM算法求的是最佳匹配,即在匹配是完备的基础上权值之和最大。这和带权最大匹配是不一样的,不过我们可以加入若干条边权为0的边使得KM求出来的最佳匹配等于最大权匹配。具体实现的时候最好用矩阵来存图,因为一般点的个数都是10^2级别,并且这样默认任意两点之间都存在边权为0的边,写起来很方便。如果要求最小权匹配,我们可以用一个很大数减去每条边的边权。

PPT可以私信我要,csdn好像没法传PPT。

这是匹配的常见知识,下面贴一下我的常见模板。

二分图匹配判断,在work稍加修改就可以求最大匹配。

bool vis[222], maze[222][222];
int n, Link[222];
bool match(int x){
    for(int i = 1; i <= n; i++){
        if(!vis[i] && maze[x][i]){
            vis[i] = 1;
            if(Link[i] == -1 || match(Link[i])){
                Link[i] = x;
                return true;
            }
        }
    }
    return false;
}
bool work(){
    for(int i = 1; i <= n; i++){
        memset(vis, 0, sizeof(vis));
        if(!match(i)) return false;
    }
    return true;
}

KM带权匹配,对了这个算法,我暂时还不懂,只能解决裸题。

int match[N],visx[N],visy[N],lx[N],ly[N],slk[N];

bool dfs(int x){
    visx[x]=1;
    for(int y=1;y<=m;y++)
        if(!visy[y]){
            int t=lx[x]+ly[y]-w[x][y];
            if(t==0){
                visy[y]=1;
                if(match[y]==0||dfs(match[y])){
                    match[y]=x;
                    return true;
                }
            }
            else
                slk[y]=min(slk[y],t);
        }
    return false;
}

int KM(){
    memset(match,0,sizeof(match));
    memset(lx,0,sizeof(lx));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) lx[i]=max(lx[i],w[i][j]);
    for(int i=1;i<=n;i++)
        while(1){
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            for(int j=1;j<=m;j++) slk[j]=inf;
            if(dfs(i)) break;
            int d=inf;
            for(int j=1;j<=m;j++) if(!visy[j]) d=min(d,slk[j]);
            for(int j=1;j<=n;j++) if(visx[j]) lx[j]-=d;
            for(int j=1;j<=m;j++) if(visy[j]) ly[j]+=d;
        }
    int ret=0;
    for(int i=1;i<=n;i++) ret+=lx[i];
    for(int i=1;i<=m;i++) if(match[i]) ret+=ly[i];
    return ret;
}

然后就是多重匹配要跑最大流和最小费用流,先贴个模板上来:

网络最大流 dinic最大流

struct G
{
    int v, cap, next;
    G() {}
    G(int v, int cap, int next) : v(v), cap(cap), next(next) {}
} E[maxm];
int p[maxn], T;
int d[maxn], temp_p[maxn], qw[maxn]; //d顶点到源点的距离标号,temp_p当前狐优化,qw队列
void init()
{
    memset(p, -1, sizeof(p));
    T = 0;
}
void add(int u, int v, int cap)
{
    E[T] = G(v, cap, p[u]);
    p[u] = T++;
    E[T] = G(u, 0, p[v]);
    p[v] = T++;
}
bool bfs(int st, int en, int n)
{
    int i, u, v, head, tail;
    for(i = 0; i <= n; i++) d[i] = -1;
    head = tail = 0;
    d[st] = 0;
    qw[tail] = st;
    while(head <= tail)
    {
        u = qw[head++];
        for(i = p[u]; i + 1; i = E[i].next)
        {
            v = E[i].v;
            if(d[v] == -1 && E[i].cap > 0)
            {
                d[v] = d[u] + 1;
                qw[++tail] = v;
            }
        }
    }
    return (d[en] != -1);
}
int dfs(int u, int en, int f)
{
    if(u == en || f == 0) return f;
    int flow = 0, temp;
    for(; temp_p[u] + 1; temp_p[u] = E[temp_p[u]].next)
    {
        G& e = E[temp_p[u]];
        if(d[u] + 1 == d[e.v])
        {
            temp = dfs(e.v, en, min(f, e.cap));
            if(temp > 0)
            {
                e.cap -= temp;
                E[temp_p[u] ^ 1].cap += temp;
                flow += temp;
                f -= temp;
                if(f == 0)  break;
            }
        }
    }
    return flow;
}
int dinic(int st, int en, int n)
{
    int i, ans = 0;
    while(bfs(st, en, n))
    {
        for(i = 0; i <= n; i++) temp_p[i] = p[i];
        ans += dfs(st, en, INF);
    }
    return ans;
}

MincostMaxflow

const int inf = 0x3FFFFFFF;
const int maxn = 2222;
struct node{
    int st, en, flow, cost, next;
    node(){}
    node(int st, int en, int flow, int cost, int next):st(st),en(en),flow(flow),cost(cost),next(next){}
}E[101000];

int num, p[maxn];
void init(){
    memset(p, -1, sizeof(p));
    num = 0;
}
void add(int st, int en, int flow, int cost){
    E[num] = node(st, en, flow, cost, p[st]);
    p[st] = num++;
    E[num] = node(en, st, 0, -cost, p[en]);
    p[en] = num++;
}
int pre[maxn];
int dis[maxn];
bool fg[maxn];
bool spfa(int st, int en)
{
    for(int i=0;i<=en;i++){
        fg[i] = 0, dis[i] = inf, pre[i]=-1;
    }
    queue<int>q;
    q.push(st);
    fg[st]=1;
    dis[st]=0;
    while(!q.empty()){
        int u = q.front(); q.pop();
        fg[u]=0;
        for(int i=p[u];~i;i=E[i].next){
            int v = E[i].en;
            if(E[i].flow&&dis[v]>dis[u]+E[i].cost){
                dis[v] = dis[u]+E[i].cost;
                pre[v]=i;
                if(!fg[v]){
                    fg[v]=1;
                    q.push(v);
                }
            }
        }
    }
    if(dis[en] < inf) return 1;
    return 0;
}

int solve(int st, int en){
    int ans=0;
    while(spfa(st,en)){
        int d = inf;
        for(int i=pre[en];i+1;i=pre[E[i].st]) d = min(d, E[i].flow);
        for(int i=pre[en];i+1;i=pre[E[i].st]){
            E[i].flow -= d;
            E[i^1].flow += d;
            ans += d*E[i].cost;
        }
    }
    return ans;
}

今天用匈牙利算法计算二分匹配被卡了,所以拔了另一种求二分匹配的方法 Hopcroft-Karp优化

HDU 2389

算法原理:为了降低时间复杂度,可以在增广匹配集合M时,每次寻找多条增广路径。这样就可以进一步降低时间复杂度,可以证明,算法的时间复杂度可以到达O(n^0.5*m),虽然优化不了多少,但在实际应用时,效果还是很明显的。

算法介绍:该算法主要是对匈牙利算法的优化,在寻找增广路径的时候同时寻找多条不相交的增广路径,形成极大增广路径集,然后对极大增广路径集进行增广。在寻找增广路径集的每个阶段,找到的增广路径集都具有相同的长度,且随着算法的进行,增广路径的长度不断的扩大。可以证明,最多增广n^0.5次就可以得到最大匹配。

(1)从G=(X,Y;E)中取一个初始匹配。

(2)若X中的所有顶点都被M匹配,则表明M为一个完美匹配,返回;否则,以所有未匹配顶点为源点进行一次BFS,标记各个点到源点的距离。

(3)在满足dis[v] = dis[u] + 1的边集(v,u>中,从X中找到一个未被M匹配的顶点x0,记S = {x0},T = ¢。

(4)若N(S) = T,则表明当前已经无法得到更大匹配,返回;否则取一y0∈N(S) - 。

(5)若y0已经被M匹配则转步骤(6),否则做一条x0->y0的M-增广路径P(x0,y0),取M = M△P(x0,y0)。

(6)由于y已经被M匹配,所以M中存在一条边(y0,z0)去S = S∪ {z0},T = T∪{y0},转步骤(2)。

算法分析:

在寻找增广路径中可以对X中的每个未匹配的顶点进行BFS,BFS时对每个顶点维护一个距离编号dx[nx],dy[ny],如果某个Y中的节点为未匹配点,则找到一条增广路径。BFS结束后找到了增广路径集。然后利用DFS与匈牙利算法类似的方法对每条增广路进行增广,这样就可以找到最大匹配。

HDU2398

题意:在一个二维坐标系上有nx个人和ny把伞,每个人都有自己的移动速度,问有多少人可以再tmin内移动到不同的雨

伞处(不允许两个人共用一把伞)。

解法:

很容易可以看出,这是一个二分图模型,雨伞和人一一对应,典型的匹配问题,而又要求最大,所以是二分最大匹配问

题,再看看题目的数据量,nx:3000,ny:3000,极限情况下有9000000条边,很明显,匈牙利算法可能会TLE,所以

为了降低时间复杂度,我们由每次寻找一条增广路径扩展到寻找多条增广路径,这就跟Dinic与连续增广路的关系很相
似。

如何建图呢?只要满足dist(a[i], a[j]) <= si*T的点连一条边即可。

Hopcroft-Karp算法可以多次寻找增广路径,这样迭代的次数最多为2n^0.5,所以算法优化到了O(n^0.5*m)。

给出这个题代码作为模板,代码不是我写的,是http://blog.csdn.net/wall_f/article/details/8248350
这位博主的,创作不易,尊重知识产权从我做起。

    #include <iostream>  
    #include <cstdlib>  
    #include <cstdio>  
    #include <cstring>  
    #include <queue>  
    #include <cmath>  
    using namespace std;  

    const int MAXN = 3010;  
    const int MAXM = 3010*3010;  
    const int INF = 0x3f3f3f3f;  

    struct Edge  
    {  
        int v;  
        int next;  
    }edge[MAXM];  

    struct node  
    {  
        double x, y;  
        double v;  
    }a[MAXN], b[MAXN];  

    int nx, ny;  
    int cnt;  
    int t;  
    int dis;  

    int first[MAXN];  
    int xlink[MAXN], ylink[MAXN];  
    int dx[MAXN], dy[MAXN];  
    int vis[MAXN];  

    void init()  
    {  
        cnt = 0;  
        memset(first, -1, sizeof(first));  
        memset(xlink, -1, sizeof(xlink));  
        memset(ylink, -1, sizeof(ylink));  
    }  

    void read_graph(int u, int v)  
    {  
        edge[cnt].v = v;  
        edge[cnt].next = first[u], first[u] = cnt++;  
    }  

    int bfs()  
    {  
        queue<int> q;  
        dis = INF;  
        memset(dx, -1, sizeof(dx));  
        memset(dy, -1, sizeof(dy));  
        for(int i = 0; i < nx; i++)  
        {  
            if(xlink[i] == -1)  
            {  
                q.push(i);  
                dx[i] = 0;  
            }  
        }  
        while(!q.empty())  
        {  
            int u = q.front(); q.pop();  
            if(dx[u] > dis) break;  
            for(int e = first[u]; e != -1; e = edge[e].next)  
            {  
                int v = edge[e].v;  
                if(dy[v] == -1)  
                {  
                    dy[v] = dx[u] + 1;  
                    if(ylink[v] == -1) dis = dy[v];  
                    else  
                    {  
                        dx[ylink[v]] = dy[v]+1;  
                        q.push(ylink[v]);  
                    }  
                }  
            }  
        }  
        return dis != INF;  
    }  

    int find(int u)  
    {  
        for(int e = first[u]; e != -1; e = edge[e].next)  
        {  
            int v = edge[e].v;  
            if(!vis[v] && dy[v] == dx[u]+1)  
            {  
                vis[v] = 1;  
                if(ylink[v] != -1 && dy[v] == dis) continue;  
                if(ylink[v] == -1 || find(ylink[v]))  
                {  
                    xlink[u] = v, ylink[v] = u;  
                    return 1;  
                }  
            }  
        }  
        return 0;  
    }  

    int MaxMatch()  
    {  
        int ans = 0;  
        while(bfs())  
        {  
            memset(vis, 0, sizeof(vis));  
            for(int i = 0; i < nx; i++) if(xlink[i] == -1)  
            {  
                ans += find(i);  
            }  
        }  
        return ans;  
    }  

    /*double dist(const node a, const node b) //TLE,无力吐槽了 
    { 
        return sqrt(pow((a.x-b.x), 2.0) + pow((a.y-b.y), 2.0)); 
    }*/  

    double dist(const node a, const node b)  
    {  
        return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));  
    }  

    void read_case()  
    {  
        init();  
        int Time;  
        scanf("%d", &Time);  
        scanf("%d", &nx);  
        for(int i = 0; i < nx; i++)  
        {  
            scanf("%lf%lf%lf", &a[i].x, &a[i].y, &a[i].v);  
        }  
        scanf("%d", &ny);  
        for(int i = 0; i < ny; i++)  
        {  
            scanf("%lf%lf", &b[i].x, &b[i].y);  
        }  
        for(int i = 0; i < nx; i++)  
        {  
            for(int j = 0; j < ny; j++)  
            {  
                double limit = a[i].v*Time;  
                double s = dist(a[i], b[j]);  
                if(s <= limit) read_graph(i, j);  
            }  
        }  
    }  

    void solve()  
    {  
        read_case();  
        int ans = MaxMatch();  
        printf("%d\n\n", ans); //注意格式   
    }  

    int main()  
    {  
        int T, times = 0;  
        scanf("%d", &T);  
        while(T--)  
        {  
            printf("Scenario #%d:\n", ++times);  
            solve();  
        }  
        return 0;  
    }  

“`

然后推荐一些题目:

HDU 3729 二分图匹配(一边点数很小,一边点数很大)
HDU 3729 二分图匹配(有字典序要求) 题解:http://blog.csdn.net/just_sort/article/details/70182521
UVA 11985 需要一点优化的二分图匹配

题意:找出一个集合中的最大独立集,任意两数字之间不能是素数倍数的关系。

最大独立集,必然是二分图。

最大数字50w,考虑对每个数质因子分解,然后枚举所有除去一个质因子后的数是否存在,存在则建边,考虑到能这样建边的数一定是质因子个数奇偶不同,所以相当于按奇偶区分建立了二分图,然后求二分图最大匹配,得到最大独立集就行了。
有一点这个题数据比较大,直接匈牙利炸了,要Hopcroft-Karp(只会套板)优化才能过。
说实话,卡匈牙利确实不像话,网络流可以卡过?

接下来是一些比较脑洞或者神的匹配,KM由于只会裸题,就没列例题了。

POJ 1698 二分图多重最大匹配

题解 : http://blog.csdn.net/just_sort/article/details/70183134

POJ 2112 二分图多重最大匹配+最大流+二分求极值

http://blog.csdn.net/just_sort/article/details/70183390

BZOJ 1059 二分匹配(脑洞,证明要用 线形代数的知识)

http://blog.csdn.net/just_sort/article/details/54694160

BZOJ 1143 最大独立集

http://blog.csdn.net/just_sort/article/details/55096520

CF 687D 二分图最大匹配(也可以用带权并查集求)

http://blog.csdn.net/just_sort/article/details/60144952

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值