二分图中的可行边与必须边—求法

定义

二分图的最大匹配的方案可能是有多种的,其中无论如何都要选的边叫必须边;可选可不选,选了也能够得到最大匹配的叫可行边。

简化版:完备匹配

在能够完备匹配的二分图中,求必须边和可行边相对简单,我们先来讨论这个问题。
先对这个二分图(左边右边都是n个点)做一次最大匹配,现在所有的点都是匹配点了,匹配边有n条。
在此图中,把匹配边反过来,即从右往左连,非匹配边不变,仍是从左往右。

一条边(x,y)是可行边,只要满足一项
1、其已是匹配边;
2、断开(x,u)和(v,y),连接(x,y)后,能找到一条从v到u的增广路。(这样才能保证最大匹配不变)
对于第2条,要求能找到一条从v到u的增广路,也就是说在新图中有v -> … -> u的一条路径。因为本来就有x -> y、u -> x和y -> v(注意匹配边要反过来),连起来就是x -> y -> v -> … -> u -> x的一个环啊。理解一下,不难发现就是要一条“匹配边,非匹配边,……,匹配边,非匹配边”的路径上的所有匹配与非匹配取反,结果是出现了另一种最大匹配方案。所以条件可以简化成边(x,y)是非匹配边,但(x,y)处于某个环中,则(x,y)是可行边。用更专业的、便于实现的话来说,边(x,y)是非匹配边,但x和y处在同一个连通分量中,则(x,y)是可行边。

一条边(x,y)是必须边,要求
1、其已是匹配边;
2、删除(x,y)后,无法找到一条从x到y的增广路。(所以最大匹配会减小1)
第二条用可行边的表述方法,可以说成,或者边(x,y)是匹配边,且(x,y)不处于某个环中,则(x,y)是必须边;或者边(x,y)是匹配边,且x和y不处在同一个连通分量中,则(x,y)是必须边。

所以对于完备匹配求可行边和必须边的问题,只要做一次最大匹配后跑一次tarjan,就可以求出。

问题升级!对于任意一个二分图呢?

那么会出现2种特殊的情况:
1、边(x,y)非匹配,但x是匹配点,y非匹配。此时断开与x匹配的边,连接(x,y)又是一种可行边方案。
2、边(x,y)非匹配,但x、y都是匹配点。假设与x,y匹配的边为(x,u)和(v,y)。强制断开(x,u)和(v,y),连接(x,y)会导致最大匹配数-1。此时最好的方法当然是在v和u中能找到一条增广路,让(v,u)连接。实际上v和u并不一定要连接,它们可以另外找个点,组成一组匹配。也就是说让v和u各自找增广路,且增广路中不要求包含对方。根据最大匹配的定义,显然要么只有u能找到一条增广路,要么v能找到,反正只要有一个能找到(x,y)就是可行边。

读者应该了解过,二分图匹配的问题可以用网络流解决。对于开头提到的“把匹配边反过来”,其实就是网络流中的方向边。请读者自行把二分图匹配的图转化成网络流的图。
使用网络流,我们要额外增加S和T节点。对于特殊情况2,我们再来琢磨琢磨。

假设v找到了一条增广路,最终找到的一个未匹配点是z。那么有(z,T)的剩余容量为1,(u,T)为0,即(T,u)为1,(u,v)为1。发现存在一条v -> z -> T -> u,和一条u -> v的路径,所以u和v处在一个连通分量中,用二分图匹配的话说就是找到了一条从v到u的增广路。也就是说,借助T点,我们让u和v实现了连通。

总结

综上,在用网络流替代、新增了S和T节点后,我们在完备匹配中的结论又活过来了!
先一次网络流,再跑一次tarjan求连通分量。
边(x,y)是可行边,要求:(x,y)已是匹配边    ||  (   (x,y)是非匹配边  &&  x和y处在同一个强连通分量中   )。
边(x,y)是必须边,要求:(x,y)已是匹配边   &&  x和y属于不同的强连通分量中。

例题

CH#17C 舞动的夜晚

题解
求 可行边U必须边 的补集,算是模板题了~

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define id(i,j) i*n+j
using namespace std;
const int inf=(1<<30)-1;
const int maxn=10010,maxt=100010;
const int maxp=maxn*2,maxe=maxt+maxp;

int n,m,t;

struct E{int y,c,next;}e[maxe*2];int len=1,lent,last[maxp];//记得调整大小!! 
void ins(int x,int y,int c)
{
    e[++len]=(E){y,c,last[x]};last[x]=len;
    e[++len]=(E){x,0,last[y]};last[y]=len;
}

int S,T;
int h[maxp];
int head,tail,q[maxp];
bool bfs()
{
    head=0,tail=1;q[0]=S;
    memset(h,0,sizeof(h));h[S]=1;
    while(head<tail)
    {
        int x=q[head++];
        for(int k=last[x];k;k=e[k].next)
        {
            int y=e[k].y;
            if(h[y]==0 && e[k].c)
            {
                h[y]=h[x]+1;
                q[tail++]=y;
            }
        }
    }
    return h[T]>0;
}

int dinic(int x,int flow)
{
    if(x==T) return flow;
    int rest=flow,minf;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(e[k].c && h[y]==h[x]+1)
        {
            minf=dinic(y,min(rest,e[k].c));
            rest-=minf;
            e[k].c-=minf;e[k^1].c+=minf;
            if(rest==0) break;
        }
    }
    if(rest==flow) h[x]=0;
    return flow-rest;
}

int id,dfn[maxp],low[maxp];
int top,sta[maxp];bool v[maxp];
int scc,bel[maxp];
void tarjan(int x)
{
    dfn[x]=low[x]=++id;
    sta[++top]=x;v[x]=true;
    for(int k=last[x];k;k=e[k].next)
    {
        int y=e[k].y;
        if(!e[k].c) continue;
        if(dfn[y]==0)
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
        }
        else if(v[y]) low[x]=min(low[x],dfn[y]);
    }
    if(low[x]==dfn[x])
    {
        scc++;int i;
        do
        {
            i=sta[top--];
            v[i]=false;
            bel[i]=scc;
        }while(i!=x);
    }
}

int ac,aq[maxp];
int main()
{
    scanf("%d%d%d",&n,&m,&t);
    for(int i=1;i<=t;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        ins(id(0,x),id(1,y),1);
    }
    lent=len;
    S=0,T=n+m+1;
    for(int i=1;i<=n;i++) ins(S,id(0,i),1);
    for(int i=1;i<=m;i++) ins(id(1,i),T,1);
    
    int maxflow=0;
    while(bfs()) maxflow+=dinic(S,inf);
    
    for(int i=S;i<=T;i++)
        if(!dfn[i]) tarjan(i);//debug if(dfn[i])
    
    for(int i=2;i<=lent;i+=2)
    {
        int x=e[i^1].y,y=e[i].y;
        if(e[i].c==1 && bel[x]!=bel[y])//非匹配边 & 不在一个联通分量 
            aq[++ac]=i/2;
    }
    
    printf("%d\n",ac);
    for(int i=1;i<=ac;i++) printf("%d ",aq[i]);
    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值